最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

世界快播:手工实现一个ORM小框架

来源:博客园

手工实现一个ORM框架

目的

温故而知新

了解ORM框架实现的基本原理

与面试官吹B


(相关资料图)

本项目所需前置知识

  • 注解@interface
  • 泛型
  • JDBC

相关知识简介

认识JDBC

JDBC是什么?

Java DateBase connectivity:Java数据库连接,Java语言连接数据库JDBC本质:利用Java语言连接数据库、操作数据库的一整套接口规范

为什么存在JDBC?

屏蔽底层的技术实现细节,让程序员专注于业务代码。说人话就是:连接mysql数据库时就用mysql厂商提供的jdbc实现;而连接oracle数据库时就用oracle厂商提供的jdbc实现。基于同一套接口规范调用不同厂商实现的jdbc,来实现连接不同的数据库。

举个不太恰当的例子:人饿了需要吃饭,吃饭一般步骤可以归纳为以下几步:1.排队 2.点餐 3.付款 4.吃饭,基于这4步(规范),我们便可以轻车熟路的填饱肚子:想吃汉堡就去肯德基、麦当劳;想吃拉面就去兰州拉面;想吃火锅就去火锅店。而不必担心吃汉堡需不需要带叉子,吃拉面需不需要带筷子,吃大饼需不需要带手套。

如何使用JDBC操作数据库

  • 数据库连接通用方法

    public class DBUtil {    private static String URL = "jdbc:mysql://node1:3306/demo";    private static String USERNAME = "user";    private static String PASSWORD= "Password@123";    private DBUtil() {        throw new RuntimeException("can not instance.");    }    public static Connection getConnection() {        Connection connection = null;        try {            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);        } catch (SQLException e) {            System.out.printf("获取数据库连接异常:%s",e);        }        return connection;    }    public static void closeConnection(Connection c, Statement s) {        try {            if (s != null) {                s.close();            }            if (c != null) {                c.close();            }        } catch (SQLException e) {            System.out.printf("关闭数据库连接异常:%s",e);        }    }    public static void closeConnection(Connection c, Statement s, ResultSet rs) {        try {            if (rs != null) {                rs.close();            }            if (s != null) {                s.close();            }            if (c != null) {                c.close();            }        } catch (SQLException e) {            System.out.printf("关闭数据库连接异常:%s",e);        }    }}
  • 获取数据库连接

    public class DBUtilTest {    @Test    public void getConnection() {        Connection connection = DBUtil.getConnection();        Assert.assertNotNull(connection);    }}
  • 通过数据库连接查询表数据

    public class DBUtilTest {    @Test    public void queryTableRow() throws SQLException {        String sql = "select * from user where id=?";        Connection connection = DBUtil.getConnection();        PreparedStatement pst = connection.prepareStatement(sql);        // 查询ID为1的用户数据        pst.setObject(1, 1L);        ResultSet rs = pst.executeQuery();        Assert.assertNotNull(rs);        while (rs.next()) {            // 用户表中的每一行数据存在7列            for (int i = 1; i <= 7; i++) {                String columnName = rs.getMetaData().getColumnName(i);                Object columnValue = rs.getObject(i);                System.out.printf("column: %s, value:%s\n", columnName, columnValue);            }        }    }}
  • 封装一个DAO对象

    我们知道在Java中一切皆对象,同样的,在对数据源的访问操作,自然应该进行抽象和封装。基于此,DAO(Data Access Object)便诞生了。

    1. 首先创建一个实体对象,与数据库中的行对应

      public class User {    /**     * 自增主键     */    private Long id;    /**     * 用户名     */    private String username;    /**     * 密码     */    private String password;    /**     * 盐     */    private String salt;    /**     * 身份证     */    private String identityCard;    /**     * 创建时间     */    private LocalDateTime createTime;    /**     * 更新时间     */    private LocalDateTime updateTime;    // getter、setter...}
    2. 其次创建一个DAO对象,用于操作数据库

      public class UserDao {    public User queryUserById(Long id) throws SQLException {        String sql = "select * from user where id=?";        Connection connection = DBUtil.getConnection();        PreparedStatement pst = connection.prepareStatement(sql);        pst.setObject(1, id);        ResultSet rs = pst.executeQuery();        while (rs.next()) {            User user = new User();            user.setId(rs.getLong(1));            user.setUsername(rs.getString(2));            user.setPassword(rs.getString(3));            user.setSalt(rs.getString(4));            user.setIdentityCard(rs.getString(5));            user.setCreateTime(rs.getObject(6, LocalDateTime.class));            user.setUpdateTime(rs.getObject(7, LocalDateTime.class));            return user;        }        return null;    }        // create、update、delete...}
    3. 最后,编写单元测试

      public class UserDaoTest {    @Test    public void queryUserById() throws SQLException {        UserDao userDao = new UserDao();        User user = userDao.queryUserById(1L);        Assert.assertNotNull(user);    }}

    至此,一个简单的DAO对象便封装完成了。

认识泛型

泛型是什么?

泛型:就是指在类定义时不会设置类中的属性或方法参数的具体类型,而是在类使用时(创建对象)再进行类型的定义。会在编译期检查类型是否错误。

如何使用泛型

  • 创建泛型类

    public class BaseDao {    private T t;    public T getT() {        return t;    }    public void setT(T t) {        this.t = t;    }}
  • 编写测试用例

    public class BaseDaoTest {    @Test    public void setGetT() {        BaseDao baseDao = new BaseDao<>();        baseDao.setT(123456);        Assert.assertEquals(123456, (int) baseDao.getT());        BaseDao baseDao2 = new BaseDao<>();        User user = new User();        user.setId(1L);        user.setUsername("admin");        baseDao2.setT(user);        Assert.assertEquals(1L, (long) baseDao2.getT().getId());        Assert.assertEquals("admin", baseDao2.getT().getUsername());    }}
  • 创建泛型方法

    public class BaseDao {        public  T get(T t) {        return t;    }}
  • 编写测试用例

    public class BaseDaoTest {    @Test    public void setGet() {        BaseDao baseDao = new BaseDao();        Integer i = baseDao.get(123456);        Assert.assertEquals(123456, (int) i);        String s = baseDao.get("123456");        Assert.assertEquals("123456", s);        User user = new User();        user.setId(1L);        user.setUsername("admin");        User u = baseDao.get(user);        Assert.assertEquals(1L, (long) u.getId());        Assert.assertEquals("admin", u.getUsername());    }}
  • 泛型可以做什么

    编译期进行强制类型检查,避免强制类型转换产生的不必要错误,增加程序的健壮性。下面看个例子:

    1. 泛型情况下从List集合中获取数据

      public class GenericTest {    @Test    public void queryFromList() {        List users = new ArrayList<>();        for (int i = 0; i < 5; i++) {            User user = new User();            user.setId((long) i);            user.setUsername(String.format("test%d", i));            users.add(user);        }        for (User user : users) {            System.out.printf("id: %d, username:%s\n", user.getId(), user.getUsername());        }    }}
    2. 非泛型情况下从List集合中获取数据

      public class GenericTest {    @Test    public void queryFromList() {        List users = new ArrayList();        for (int i = 0; i < 5; i++) {            User user = new User();            user.setId((long) i);            user.setUsername(String.format("test%d", i));            users.add(user);        }        for (Object user : users) {            // User u = (User) user; 错误的强制类型转换并不会在编译器出现任何的提示,只有在程序的运行过程中才会出现java.lang.ClassCastException            // 下面这个错误的类型转换,可能是Ctrl-c、Ctrl-v程序员最常见的错误            USer u = (USer) user;            System.out.printf("id: %d, username:%s\n", u.getId(), u.getUsername());        }    }}
    3. 在父类中获取子类的泛型信息

      // 1.获取泛型信息的接口public interface GenericUtil {    /**     * 获取泛型信息     *     * @return T 对应的具体class信息     */    @SuppressWarnings("unchecked")    default Class getGeneric() {        Type genericSuperclass = this.getClass().getGenericSuperclass();        if (genericSuperclass instanceof ParameterizedTypeImpl                && ((ParameterizedType) genericSuperclass).getRawType().equals(BaseDao.class)) {            return (Class) ((ParameterizedType) genericSuperclass)                    .getActualTypeArguments()[0];        }        throw new RuntimeException("子类必须继承" + BaseDao.class);    }}// 2.实现GenericUtil接口的基类public class BaseDao implements GenericUtil {    public Class genericInfo() {        return getGeneric();    }}// 3.继承BaseDao的子类public class UserDao extends BaseDao {    // some other method...}
    4. 获取子类泛型信息测试用例

      public class UserDaoTest {    @Test    public void genericInfo() {        UserDao userDao = new UserDao();        Class genericInfo = userDao.genericInfo();        Assert.assertEquals(User.class, genericInfo);    }}

    通过3、4可以看出泛型的一个应用场景:父类获取子类中的泛型信息,通过解析获取到的泛型信息,可以实现通用的CURD方法。

    由此,可以看出泛型在编码过程中对程序员而言是很有帮助的:它能在一定程度上提高我们的工(mo)作(yu)效率。但泛型的作用远不止于此。下文将会结合注解,编写一个基本的BaseDao,来实现一个ORM框架的基本功能。

认识注解

注解是什么?

一种代码级别的注释。可以在源代码级、class文件或者运行时中出现。

如何使用注解

  • 创建一个自定义注解

    @Target(ElementType.FIELD) // 标识注解只能出现在类的属性上@Retention(RetentionPolicy.RUNTIME) // 标识注解可以在程序运行时被获取到public @interface Column {    String value();}
  • 使用注解

    public class User {    /**     * 自增主键     */    private Long id;    /**     * 用户名     */    @Column("username")    private String username;    /**     * 密码     */    @Column("password")    private String password;    /**     * 盐     */    @Column("salt")    private String salt;    /**     * 身份证     */    @Column("identity_card")    private String identityCard;    /**     * 创建时间     */    @Column("create_time")    private LocalDateTime createTime;    /**     * 更新时间     */    @Column("update_time")    private LocalDateTime updateTime;    // getter、setter...}
  • 利用反射获取注解信息

    public class AnnotationTest {    @Test    public void getAnnotationInfo() {        Class clazz = User.class;        Field[] fields = clazz.getDeclaredFields();        for (Field field : fields) {            Column column = field.getAnnotation(Column.class);            System.out.println(column);        }    }}
  • 注解可以做什么

    拼接SQL语句,下面看个例子:

    public class AnnotationTest {    private static List getColumnAnnotations(Class clazz) {        Field[] fields = clazz.getDeclaredFields();        List columns = new ArrayList<>();        for (Field field : fields) {            Column column = field.getAnnotation(Column.class);            if (Objects.nonNull(column)) {                columns.add(column);            }        }        return columns;    }    @Test    public void generateSQLByAnnotationInfo() {        List columnAnnotations = getColumnAnnotations(User.class);        // 此处我们可以通过自定义一个@Table注解,来获取实体对应的表名称        String tableName = "user";        StringBuilder sql = new StringBuilder("select");        for (Column column : columnAnnotations) {            sql.append(" ").append(column.value()).append(",");        }        sql.deleteCharAt(sql.length() - 1);        sql.append(" from ").append(tableName);        // generateSQL: select username, password, salt, identity_card, create_time, update_time from user        System.out.println("generateSQL: " + sql);    }}

    通过上述例子我们可以看到:注解可以用在实体与表结构之间的映射关系上,通过注解我们可以封装基类,实现通用的CURD方法,更进一步的提高我们的工(mo)作(yu)效率。

从0开始实现一个ORM框架

本章节带领小伙伴们实现基类的封装:利用继承和泛型实现给定实体类的通用CURD方法。

一、自定义注解实现实体与表的映射

  • 与表名称关联的注解

    @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Table {    String value();}
  • 与主键关联的注解

    @Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Id {    String value() default "";}
  • 与普通列关联的注解

    @Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Column {    String value();}

    通过以上三个注解,我们便可以在对应的实体类中标识与之对应的表名称、表字段和主键信息。

二、创建实体对象关联表结构

  • 创建一个User实体并使用注解映射表结构

    @Table("user")public class User {    /**     * 自增主键     */    @Id("id")    private Long id;    /**     * 用户名     */    @Column("username")    private String username;    /**     * 密码     */    @Column("password")    private String password;    /**     * 盐     */    @Column("salt")    private String salt;    /**     * 身份证     */    @Column("identity_card")    private String identityCard;    /**     * 创建时间     */    @Column("create_time")    private LocalDateTime createTime;    /**     * 更新时间     */    @Column("update_time")    private LocalDateTime updateTime;    // getter、setter...}

三、创建工具类封装表与实体的对应关系

  • 创建EntityMapper封装表与实体的对应关系

    public class EntityMapper {    private String tableName;    private String idName;    private Map columnMapper;    // getter、setter...}
  • 创建AnnotationUtil解析实体类注解信息

    public class AnnotationUtil {    /**     * 将给定entity的注解信息,封装到EntityMapper中     */    public static EntityMapper parse(Object entity) {        EntityMapper mapper = new EntityMapper();        Class clazz = entity.getClass();        String tableName = parseTableName(clazz);        String id = parseIdName(clazz);        Map columnMapper = parseColumnName(clazz);        mapper.setTableName(tableName);        mapper.setIdName(id);        mapper.setColumnMapper(columnMapper);        return mapper;    }    private static String parseTableName(Class clazz) {        Annotation[] clazzAnnotations = clazz.getAnnotations();        if (clazzAnnotations.length > 0) {            for (Annotation clazzAnnotation : clazzAnnotations) {                if (clazzAnnotation instanceof Table) {                    return ((Table) clazzAnnotation).value();                }            }        }        return null;    }    private static String parseIdName(Class clazz) {        Field[] declaredFields = clazz.getDeclaredFields();        if (declaredFields.length > 0) {            for (Field declaredField : declaredFields) {                Annotation[] fieldAnnotations = declaredField.getAnnotations();                if (fieldAnnotations.length > 0) {                    for (Annotation fieldAnnotation : fieldAnnotations) {                        if (fieldAnnotation instanceof Id) {                            return ((Id) fieldAnnotation).value();                        }                    }                }            }        }        return null;    }    private static Map parseColumnName(Class clazz) {        Map columnMapper = new HashMap<>();        Field[] declaredFields = clazz.getDeclaredFields();        if (declaredFields.length > 0) {            for (Field declaredField : declaredFields) {                Annotation[] fieldAnnotations = declaredField.getAnnotations();                if (fieldAnnotations.length > 0) {                    for (Annotation fieldAnnotation : fieldAnnotations) {                        if (fieldAnnotation instanceof Column) {                            String value = ((Column) fieldAnnotation).value();                            columnMapper.putIfAbsent(declaredField.getName(), value);                        } else if (fieldAnnotation instanceof Id) {                            String value = ((Id) fieldAnnotation).value();                            columnMapper.putIfAbsent(declaredField.getName(), value);                        }                    }                }            }        }        return columnMapper;    }}
  • 创建SqlGenerateUtil根据EntityMapper生成SQL语句

    public class SqlGenerateUtil {    public static String generateSelectById(EntityMapper mapper) {        StringBuilder sql = new StringBuilder("SELECT ");        Set> entrySet = mapper.getColumnMapper().entrySet();        for (Map.Entry entry : entrySet) {            sql.append(entry.getValue()).append(", ");        }        sql                .deleteCharAt(sql.length() - 2)                .append("FROM ").append(mapper.getTableName())                .append(" ").append("WHERE ")                .append(mapper.getIdName()).append(" ")                .append("= ?");        return sql.toString();    }    public static String generateSelectAll(EntityMapper mapper) {        StringBuilder sql = new StringBuilder("SELECT ");        Set> entrySet = mapper.getColumnMapper().entrySet();        for (Map.Entry entry : entrySet) {            sql.append(entry.getValue()).append(", ");        }        sql                .deleteCharAt(sql.length() - 2)                .append("FROM ").append(mapper.getTableName());        return sql.toString();    }    public static String generateInsert(EntityMapper mapper) {        StringBuilder sql = new StringBuilder("INSERT INTO ")                .append(mapper.getTableName()).append(" ").append("( ");        Set> entrySet = mapper.getColumnMapper().entrySet();        for (Map.Entry entry : entrySet) {            sql.append(entry.getValue()).append(", ");        }        sql.deleteCharAt(sql.length() - 2).append(") ").append("VALUES ( ");        for (Map.Entry entry : entrySet) {            sql.append("?").append(", ");        }        sql.deleteCharAt(sql.length() - 2).append(")");        return sql.toString();    }    public static String generateUpdate(EntityMapper mapper) {        StringBuilder sql = new StringBuilder("UPDATE ")                .append(mapper.getTableName()).append(" SET ");        Set> entrySet = mapper.getColumnMapper().entrySet();        for (Map.Entry entry : entrySet) {            if (mapper.getIdName().equals(entry.getValue())) {                continue;            }            sql.append(entry.getValue()).append("=?, ");        }        sql                .deleteCharAt(sql.length() - 2)                .append("WHERE ").append(mapper.getIdName()).append("=?");        return sql.toString();    }    public static String generateDelete(EntityMapper mapper) {        StringBuilder sql = new StringBuilder("DELETE FROM ")                .append(mapper.getTableName()).append(" WHERE ")                .append(mapper.getIdName()).append("=?");        return sql.toString();    }}
  • 编写测试类

    public class EntityAnnotationTest {    @Test    public void generateSelectAll() {        User user = new User();        EntityMapper entityMapper = AnnotationUtil.parse(user);        String sql = SqlGenerateUtil.generateSelectAll(entityMapper);        System.out.println(sql);    }    @Test    public void generateSelectById() {        User user = new User();        EntityMapper entityMapper = AnnotationUtil.parse(user);        String sql = SqlGenerateUtil.generateSelectById(entityMapper);        System.out.println(sql);    }        // generateInsert、generateUpdate、generateDelete...}

    通过如上步骤,便可以根据一个给定的实体类,实现自动生成CURD通用SQL。

四、创建工具类获取数据库连接

  • 创建DBUtil从外部读取数据库配置,并生成连接

    public class DBUtil {    private static final String DEFAULT_PROPERTIES_PATH = "jdbc/db.properties";    private static final String URL;    private static final String USERNAME;    private static final String PASSWORD;    static {        try (InputStream in = DBUtil.class.getClassLoader().getResourceAsStream((DEFAULT_PROPERTIES_PATH))) {            Properties p = new Properties();            p.load(in);            URL = p.getProperty("mysql.url").trim();            USERNAME = p.getProperty("mysql.username").trim();            PASSWORD = p.getProperty("mysql.password").trim();        } catch (IOException e) {            throw new RuntimeException("[" + DEFAULT_PROPERTIES_PATH + "]读取失败.");        }    }    private DBUtil() {        throw new RuntimeException("can not instance.");    }    public static Connection getConnection() throws SQLException {        return DriverManager.getConnection(URL, USERNAME, PASSWORD);    }    public static void closeConnection(Connection c) throws SQLException {        if (c != null) {            c.close();        }    }    public static void closeConnection(Connection c, Statement s) throws SQLException {        if (s != null) {            s.close();        }        if (c != null) {            c.close();        }    }    public static void closeConnection(Statement s, ResultSet rs) throws SQLException {        if (rs != null) {            rs.close();        }        if (s != null) {            s.close();        }    }    public static void closeConnection(Connection c, Statement s, ResultSet rs) throws SQLException {        if (rs != null) {            rs.close();        }        if (s != null) {            s.close();        }        if (c != null) {            c.close();        }    }}
  • 创建db.properties配置文件

    # path: classpath:jdbc/db.propertiesmysql.url=jdbc:mysql://127.0.0.1:3306/demomysql.username=demomysql.password=123456
  • 编写测试用例

    public class DBUtilTest {    @Test    public void getConnection() throws SQLException {        Connection connection = DBUtil.getConnection();        Assert.assertNotNull(connection);    }}

    如上步骤,我们便可以在代码之外,动态切换数据库配置。

五、创建工具类优化获取数据库连接

  • 创建ConnectionManager,利用ThreadLocal来保证事务

    public class ConnectionManager {    private static final ThreadLocal connectionThreadLocal = new ThreadLocal<>();    public static Connection getConnection() {        return connectionThreadLocal.get();    }    public static void setConnection(Connection connection) {        connectionThreadLocal.set(connection);    }    public static void removeConnection() {        connectionThreadLocal.remove();    }}
  • 创建DataSourceUtil控制连接的获取和关闭

    public class DataSourceUtil {    public static Connection getConnection() {        Connection connection = ConnectionManager.getConnection();        if (connection == null) {            try {                connection = DBUtil.getConnection();                if (connection == null) {                    throw new RuntimeException("connect is null.");                }                ConnectionManager.setConnection(connection);                return connection;            } catch (SQLException e) {                throw new RuntimeException(e);            }        }        return connection;    }    public static void closeConnection() {        try {            DBUtil.closeConnection(getConnection());        } catch (SQLException e) {            throw new RuntimeException(e);        } finally {            ConnectionManager.removeConnection();        }    }}
  • 编写测试类,验证同一个方法内多次获取的连接是否相同

    public class DataSourceUtilTest {    @Test    public void getConnection() {        Connection connection1 = DataSourceUtil.getConnection();        Connection connection2 = DataSourceUtil.getConnection();        Assert.assertEquals(connection2, connection1);        // todo commit or rollback...    }}

六、优化工具类,父类方法获取子类泛型对象

  • 优化GenericUtil接口,根据子类泛型信息实例化对象

    public interface GenericUtil {    // ...    default T getInstance() {        Class clazz = getGeneric();        T t = null;        Constructor constructor = Arrays.stream(clazz.getConstructors())                .filter(c -> c.getParameters().length == 0)                .findFirst()                .orElseThrow(() -> new RuntimeException(clazz.getSimpleName() + "没有可用的无参构造器"));        try {            Object o = constructor.newInstance();            t = clazz.cast(o);        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {            e.printStackTrace();        }        return t;    }}
  • 编写测试用例

    public class GenericUtilTest {    @Test    public void getInstance() {        // UserDao extends BaseDao        UserDao userDao = new UserDao();        User instance = userDao.getInstance();        Assert.assertEquals(User.class, instance.getClass());    }}

    如上,当我们创建一个XxxDao并继承了BaseDao之后,BaseDao便拥有了实例化T的能力。

七、利用上述工具封装基类

  • 重写BaseDao,实现通用的CURD方法

    public class BaseDao implements GenericUtil {    public T selectById(Long id) {        T instance = this.getInstance();        EntityMapper mapper = AnnotationUtil.parse(instance);        String sql = SqlGenerateUtil.generateSelectById(mapper);        try {            PreparedStatement prepareStatement = DataSourceUtil.getConnection().prepareStatement(sql);            prepareStatement.setObject(1, id);            ResultSet resultSet = prepareStatement.executeQuery();            int count = resultSet.getMetaData().getColumnCount();            Map columnMapper = mapper.getColumnMapper();            while (resultSet.next()) {                for (int i = 1; i <= count; i++) {                    String columnName = resultSet.getMetaData().getColumnName(i);                    for (Map.Entry entry : columnMapper.entrySet()) {                        if (columnName.equals(entry.getValue())) {                            Field declaredField = instance.getClass().getDeclaredField(entry.getKey());                            declaredField.setAccessible(true);                            declaredField.set(instance, resultSet.getObject(i));                        }                    }                }                return instance;            }        } catch (SQLException | NoSuchFieldException | IllegalAccessException e) {            e.printStackTrace();        }        return null;    }    public List selectAll() {        T instance = this.getInstance();        EntityMapper mapper = AnnotationUtil.parse(instance);        String sql = SqlGenerateUtil.generateSelectAll(mapper);        List list = new ArrayList<>();        try {            PreparedStatement prepareStatement = DataSourceUtil.getConnection().prepareStatement(sql);            ResultSet resultSet = prepareStatement.executeQuery();            int count = resultSet.getMetaData().getColumnCount();            Map columnMapper = mapper.getColumnMapper();            while (resultSet.next()) {                for (int i = 1; i <= count; i++) {                    String columnName = resultSet.getMetaData().getColumnName(i);                    for (Map.Entry entry : columnMapper.entrySet()) {                        if (columnName.equals(entry.getValue())) {                            Field declaredField = instance.getClass().getDeclaredField(entry.getKey());                            declaredField.setAccessible(true);                            declaredField.set(instance, resultSet.getObject(i));                        }                    }                }                list.add(instance);                instance = this.getInstance();            }        } catch (SQLException | NoSuchFieldException | IllegalAccessException e) {            e.printStackTrace();        }        return list;    }    public int insert(T t) {        T instance = this.getInstance();        EntityMapper mapper = AnnotationUtil.parse(instance);        String sql = SqlGenerateUtil.generateInsert(mapper);        try {            PreparedStatement prepareStatement = DataSourceUtil.getConnection().prepareStatement(sql);            Map columnMapper = mapper.getColumnMapper();            int i = 1;            for (Map.Entry entry : columnMapper.entrySet()) {                Field declaredField = instance.getClass().getDeclaredField(entry.getKey());                declaredField.setAccessible(true);                prepareStatement.setObject(i++, declaredField.get(t));            }            return prepareStatement.executeUpdate();        } catch (SQLException | NoSuchFieldException | IllegalAccessException e) {            e.printStackTrace();        }        return 0;    }    public int update(T t) {        T instance = this.getInstance();        EntityMapper mapper = AnnotationUtil.parse(instance);        String sql = SqlGenerateUtil.generateUpdate(mapper);        try {            PreparedStatement prepareStatement = DataSourceUtil.getConnection().prepareStatement(sql);            Map columnMapper = mapper.getColumnMapper();            int i = 1;            Object idValue = null;            for (Map.Entry entry : columnMapper.entrySet()) {                Field declaredField = instance.getClass().getDeclaredField(entry.getKey());                declaredField.setAccessible(true);                if (mapper.getIdName().equals(entry.getValue())) {                    idValue = declaredField.get(t);                    continue;                }                prepareStatement.setObject(i++, declaredField.get(t));            }            prepareStatement.setObject(i, idValue);            return prepareStatement.executeUpdate();        } catch (SQLException | NoSuchFieldException | IllegalAccessException e) {            e.printStackTrace();        }        return 0;    }    public int delete(Object id) {        T instance = this.getInstance();        EntityMapper mapper = AnnotationUtil.parse(instance);        String sql = SqlGenerateUtil.generateDelete(mapper);        try {            PreparedStatement prepareStatement = DataSourceUtil.getConnection().prepareStatement(sql);            prepareStatement.setObject(1, id);            return prepareStatement.executeUpdate();        } catch (SQLException e) {            e.printStackTrace();        }        return 0;    }}
  • 编写测试类

    public class BaseDaoTest {    UserDao userDao = new UserDao();    @Test    public void selectById() {        User user = userDao.selectById(1L);        Assert.assertNotNull(user);    }    @Test    public void selectAll() {        List users = userDao.selectAll();        Assert.assertNotNull(users);    }    @Test    public void insert() {        User user = new User();        user.setId(100L);        user.setUsername("zhangsan");        user.setPassword("123456");        int rows = userDao.insert(user);        Assert.assertTrue(rows > 0);    }    @Test    public void update() {        User user = new User();        user.setId(100L);        user.setUsername("zhangsan123");        int rows = userDao.update(user);        Assert.assertTrue(rows > 0);    }    @Test    public void delete() {        int rows = userDao.delete(100L);        Assert.assertTrue(rows > 0);    }}

    至此,我们便完成了一个ORM框架的基本骨架。

附录

项目用到的SQL文件

  • 创建数据库

    -- 创建数据库create database demo character set utf8;-- 创建用户create user demo@"%" identified by "123456";-- 授权grant all privileges on demo.* to demo@"%";grant process on *.* to demo@"%";
  • 建表语句

    DROP TABLE IF EXISTS `user`;CREATE TABLE `user`(    `id`            bigint NOT NULL COMMENT "自增主键",    `username`      varchar(50)  DEFAULT NULL COMMENT "用户名",    `password`      varbinary(128) DEFAULT NULL COMMENT "密码",    `salt`          varchar(255) DEFAULT NULL COMMENT "盐",    `identity_card` varchar(18)  DEFAULT NULL COMMENT "身份证",    `create_time`   datetime     DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT "创建时间",    `update_time`   datetime     DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT "更新时间",    PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB;

关键词: 数据库连接 测试用例 创建时间