最新要闻
- 每日视讯:比亚迪仰望:那年我翻山跨海 横扫车圈无对手
- 信息:联想Yoga Book 9i双屏笔记本发布:两块13寸2.8K触摸屏
- 当前视讯!AMD锐龙7000智酷版上架!6核不过1549元 可能有惊喜
- 天天观焦点:女子吐槽智能电视会员乱象:看什么都收费
- 环球速递!国产秀肌肉!全球首款8K激光电视来了:海信打造、画质细数毛
- 世界观察:《阿凡达2》接招!国产科幻大片走出国门 《流浪地球2》将在澳新上映
- 消息!特斯拉国产车型大幅降价 副总裁陶琳回应:坚持以成本定价
- Win11 2023开年更新Build 25272发布:干掉中文版大BUG!更加流畅稳定
- 特斯拉降价 网友翻出蔚来李斌2年前视频:价格稳定是对用户负责
- 天天新消息丨智能电视视频会员一充再充!体验太差了
- Model 3要破20万节奏!特斯拉国产车型大幅降价 老车主晒图被割韭菜
- 国产特斯拉大幅降价被业内看好 带火供应链:大批概念股飞涨
- 【天天播资讯】特斯拉 2023第一个交易日:市值缩水一个推特
- 天天播报:CDPR赔偿1267万!《赛博朋克2077》集体诉讼案终于告一段落
- 当前热点-汽车博主为小米汽车打抱不平:部分媒体只会道听途说
- 全球播报:联想ThinkPhone真机亮相:经典ThinkPad涂装抢眼
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
世界快播:手工实现一个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)便诞生了。
首先创建一个实体对象,与数据库中的行对应
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...}
其次创建一个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...}
最后,编写单元测试
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()); }}
泛型可以做什么
编译期进行强制类型检查,避免强制类型转换产生的不必要错误,增加程序的健壮性。下面看个例子:
泛型情况下从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()); } }} 非泛型情况下从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()); } }}
在父类中获取子类的泛型信息
// 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...} 获取子类泛型信息测试用例
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;
-
亲测有效! Bypass V1.15.5 12306分流抢票助手 for Windows
亲测有效!BypassV1 15 512306分流抢票助手forWindows12306Bypass也就是12306分流抢票软件,是一款全...
来源: 世界快播:手工实现一个ORM小框架
【天天播资讯】AIRIOT答疑第5期|如何使用低代码业务流引擎?
亲测有效! Bypass V1.15.5 12306分流抢票助手 for Windows
每日视讯:比亚迪仰望:那年我翻山跨海 横扫车圈无对手
信息:联想Yoga Book 9i双屏笔记本发布:两块13寸2.8K触摸屏
当前视讯!AMD锐龙7000智酷版上架!6核不过1549元 可能有惊喜
天天观焦点:女子吐槽智能电视会员乱象:看什么都收费
环球速递!国产秀肌肉!全球首款8K激光电视来了:海信打造、画质细数毛
【世界快播报】保存用户登录状态之Session和JWT
【世界热闻】three.js场景地形导出到物理引擎
网站变更检测、监控、警报丨WebSite-Watcher功能简介
基于Python的K-Means遥感影像聚类
苹果iOS app上架流程
世界观察:《阿凡达2》接招!国产科幻大片走出国门 《流浪地球2》将在澳新上映
消息!特斯拉国产车型大幅降价 副总裁陶琳回应:坚持以成本定价
Win11 2023开年更新Build 25272发布:干掉中文版大BUG!更加流畅稳定
特斯拉降价 网友翻出蔚来李斌2年前视频:价格稳定是对用户负责
天天新消息丨智能电视视频会员一充再充!体验太差了
全球即时:gget: 一款强大的基因组参考数据库的高效查询工具
学习笔记——过滤器链;监听器;Servlet、Filter、Listener的注解方式开发
Model 3要破20万节奏!特斯拉国产车型大幅降价 老车主晒图被割韭菜
redhat 9.1 安装docker
天天通讯!nginx: [error] CreateFile() “D:\nginx1.20.1/logs/nginx.pid“ failed (2: The
学习笔记——过滤器的匹配规则
国产特斯拉大幅降价被业内看好 带火供应链:大批概念股飞涨
【天天播资讯】特斯拉 2023第一个交易日:市值缩水一个推特
天天播报:CDPR赔偿1267万!《赛博朋克2077》集体诉讼案终于告一段落
当前热点-汽车博主为小米汽车打抱不平:部分媒体只会道听途说
北斗授时产品(GPS北斗授时设备)加NTP时间服务器设计思路
世界最资讯丨学习笔记——过滤器、过滤器的HelloWord、过滤器生命周期
【全球新要闻】A. Greatest Convex【Codeforces Round #842 (Div. 2)】
【天天速看料】如何安全的保存用户密码
焦点热讯:灵雀云入选2022 EDGE AWARDS「创新场景50」年度最佳场景实践榜单
全球播报:联想ThinkPhone真机亮相:经典ThinkPad涂装抢眼
【世界独家】恒驰5首次OTA升级来了!低温续航性能提升
当前消息!超越日本!印度成全球第三大汽车市场 平均千人36辆
吴京参演 《中国乒乓之绝地反击》定档大年初一:春节档已有7部新片
男子和白骆驼合影被攻击!专家提醒:骆驼战斗力惊人
环球热文:使用python编写端口扫描工具
QFramework v1.0 使用指南 工具篇:15. 补充内容:GridKit 二维格子数据结构
速读:【从零开始学爬虫】采集食品行业最新报价数据
Redmi 12C支持内存扩展:4GB内存手机瞬间变6GB 699元性价比更高了
全球消息!RTX 4050加持!联想发布Yoga AIO 9i一体机:设计惊艳
国产车型大降价 新款特斯拉Model X/S售价公布:超100万
【焦点热闻】小米13被低估了!网友没想到小米13相机能有这么大惊喜
天天日报丨三件套抄底:李锦记锦珍大桶生抽+金蚝油+黄豆酱 19.6元
每日热闻!数据结构:ST表 学习笔记
快资讯:ESP32 I2C 总线主模式通信程序
天天观点:创新的概念、设计和生产鞋类和鞋类软件丨Jevero及Botcha 3D功能简介
学习笔记——书城项目第五阶段之购物车数量的修改、精度问题的处理
世界观速讯丨《人民日报》炮轰手机预装应用不能删:占内存、鸡肋、广告满天飞
【当前热闻】240W闪充卷王!真我GT Neo5即将登场:有两种版本
全球动态:TCL华星宣布品牌形象升级:全新Logo正式上线
12月汽车投诉榜:丰田包揽前三甲 踩的同一个“坑”
世界快资讯:PS5主机千万别再长期竖向放置了!维修人士:会导致APU液金泄露 造成永久损坏
当前关注:阿汤哥驾F14爽片《壮志凌云2》惜败!《阿凡达2》成2022年票房冠军 赢麻
快资讯丨回顾2022
全球简讯:春节放假7天要调休!除夕火车票明日开抢
骁龙8 Gen2首发!高通正式推出卫星通信:3秒发出信息、可实现双向收发
每日视讯:Intel怕吗!AMD锐龙7000史上最强核显 这性能不得了
好用的工具
世界最资讯丨Python中的注释和input函数的使用
世界视讯!被骗好久!宰相刘罗锅真的是一个罗锅吗?1.9米帅男一枚
环球视讯!5位退役也未曾飞天的航天员首次公开!他们也是英雄
天天精选!在我电脑里 这是唯一的一个360产品
存款贬值快一半!土耳其人把游戏当成了救命稻草
顺序结构及if选择结构
环球今亮点!左偏树 学习笔记
亲测有效! Scrutiny 网站SEO检测及优化工具 V12.6.1 for mac
全球聚焦:创建型模式——前言
continue跳過循環(skippaart程序),接受設定的合法分數來進行平均分求值,并展現最高分,最低分
AMD锐龙7000 3D版暴力堆料144MB缓存 微软神助攻:Win11专属优化
【环球新视野】大疆2022年之最:有人每天飞5小时 有人一年飞了3万公里
正点原子FSMC控制TFT-LCD的地址偏移解读
【全球新视野】linux 下 mongodb 安装
PG中级证书到手,PostgreSQL(PG)认证
每日看点!RX 7900 XT悄然降价:国行回归7399元
【环球播资讯】AMD锐龙7000送上史上最强核显!频率3GHz 超越所有独显
世界播报:上海一家网吧春节促销:300元包20天 玩家签“生死状”
头条:风靡全国后 老头乐要席卷世界了
【天天报资讯】AcWing.1175 最大半连通子图
如何给所有的 await async 函数添加try/catch?
世界微资讯!mybatis使用postgresql中的jsonb数据类型
世界报道:2022年新能源车销量出炉:每卖出3辆车就有1辆新能源
全球动态:高分国漫《中国奇谭》B站播放量破1800万 网友:碾压《三体》动画
今年上映!国产航空大片《长空之王》新海报:尖端战机亮眼
超过6万台!国产龙芯CPU采购大单拿到手软
四川女子回娘家屋旁偶遇大熊猫直言激动 并不怕人:圆滚滚超可爱
学习笔记——书城项目第五阶段之购物项加号、购物项减号
【世界快播报】记一次2022某地HVV中的逆向分析
全球看点:2022年押注特斯拉亏大了!50支重仓基金惨不忍睹
环球视讯!加州新法实施 特斯拉仍宣传“全自动驾驶”:或被罚数千万美元
当前资讯!15分钟出结果 快速检测试剂盒:25份到手74.9元
司机酒后睡着 原地踩油门致车辆起火:烧成废墟
焦点播报:奇瑞营销公司总经理:自家2.0T发动机能与奔驰、宝马媲美
docker network
比亚迪高端品牌“仰望”发布 超级技术叩开百万级新能源市场大门
观察:首次集成AI引擎 AMD对锐龙7000移动版极其自信:超越苹果不是问题
新年微信红包封面领取攻略 谁还没有几个红包皮呢?
大桶更尽兴 溜溜梅六味青梅宝藏桶480g 25.1元