前面我们使用JDBC以及一些设计模式来完成数据的持久化操作,还是有大量的sql语句以及设置等操作,针对这些持久化操作能否以一种操作对象的方式来完成呢,即对外隐藏jdbc的实现细节,对方法api调用者来说只需要以操作对象的方式来调用就可以了。为了达到此目标,设计简易版的ORM映射框架。
默认映射规则
没有特殊说明,类的简单名称对应关系数据库的表明,如果不一致,我们自定义一个注解@Table对表名进行映射。同理,对象属性名称默认对应关系表的字段,如果不一致,我们自定义一个注解@Column对列明进行映射说明。同时我们还需要指定id的定义@Id用来映射关系表的主键
- @Table的定义:用来映射类和表名的对应关系
package com.wise.tiger.annotation; import java.lang.annotation.*; /** * 自定义注解 * 注解相当于配置说明,本身不具备任何功能,功能体现在程序中对该注解的处理 * * 元注解 * @Retention :注解保留的阶段 * SOURCE:源代码阶段 * CLASS:字节码阶段 * RUNTIME:运行时阶段 * @Target : 该注解可以贴在什么地方 * value = {}:表示属性value取值是一个数组,如果只有一个取值,那么{} * @Documented * 属性 * 数据类型 属性名(); * 可以给属性指定默认值: String name() default "";使用注解时如果没有指定属性值,那么会取默认值 * 属性可以指定多个,如果只有一个属性,且该属性名为value,那么可以不用写value = * * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface Table { String value();//表名 }
- @Column的定义,用来映射属性和列名的对应关系
package com.wise.wise.annotation; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface Column { /** * 属性名和数据库字段的映射 * @return 数据库表字段名称 */ String value(); }
- @Id的定义:用来定义主键id
package com.wise.wise.annotation; import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.FIELD}) @Documented public @interface Id { /** * id名称 * @return id名称 */ String value() default "id"; }
实体元数据的定义
package com.wise.domain; import com.wise.annotation.Column; import com.wise.annotation.Id; import com.wise.annotation.Table; import java.time.LocalDate; /** * 图书实体 */ @Table("tb_book") public class Book{ /** * id */ private Integer id; /** * 图书名称 */ private String title; /** * 图书作者 */ private String author; /** * 图书价格 */ private float price; /** * 出版社信息 */ private String publisher; /** * 图书简介 */ private String intro; /** * 出版日期 */ private LocalDate publishDate = LocalDate.now(); @Column("publish_date") public LocalDate getPublishDate() { return publishDate; } //******************setter and getter ****************// }
设计持久化操作api
- persist(T entity):将实体对象进行持久化(保存进数据库)
- remove(Serializable id,Class<?> clazz):根据id删除对应的特定类型数据,具有缓存功能
- merge(T entity):修改实体
- T findById(Serializable id):根据主键(id)获取对应的实体信息
- List<T> list(Class<T> clazz,int offset,int size):根据偏移量和每次加载记录数分页查询
- long getCount(Class<?> clazz):获取特定类型总记录数
对外开放sql语句(本地sql)的两个api
- executeUpdate
- executeQuery
public class SqlSession { /** * 执行dml语句模板方法 * @param sql 传递的sql语句 * @param params 预编译语句的站位参数 * @return 影响数据行数 */ public int executeUpdate(String sql,Object... params){ var ret = 0; try(var conn = DBHelper.getConnection(); var ptst = conn.prepareStatement(sql)){ for(int i = 0; params.length > 0 && i < params.length; i++) ptst.setObject(i + 1,params[i]); ptst.executeUpdate(); }catch (SQLException e){ e.printStackTrace(); } return ret; } /** * 执行dql语句模板方法 * @param sql 传递的sql语句 * @param handler 结果集处理handler(策略模式) * @param params 预编译语句的站位参数 * @param <T> 参数化类型 * @return 查询结果 */ public <T> T executeQuery(String sql, ResultSetHandler<T> handler, Object... params){ T ret = null; try(var conn = DBHelper.getConnection(); var ptst = conn.prepareStatement(sql)){ for(int i = 0; params.length > 0 && i < params.length; i++) ptst.setObject(i + 1,params[i]); var rs = ptst.executeQuery(); ret = handler.handler(rs); }catch (SQLException e){ e.printStackTrace(); } return ret; } /** * 添加实体数据到数据库 * @param entity 实体信息 * @return 影响行数,后期可以返回自增的主键 */ public <T> int insert(T entity){ //生成具体的插入的sql语句 try { var sp = insertSqlAndParams(entity); System.out.println(sp.getSql()); System.out.println(Arrays.toString(sp.getParams())); return executeUpdate(sp.getSql(),sp.getParams()); } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) { e.printStackTrace(); } return 0; } /** * 根据主键primary key(id)删除指定数据 * @param id 主键id * @return 影响行数 */ public int delete(Serializable id,Class<?> clazz){ var sql = "DELETE FROM " + getTableName(clazz) + " WHERE " + getIdName(clazz) + " = ?"; return executeUpdate(sql,id); } /** * 根据主键(id)修改对应的信息 * @param entity 修改好的实体信息 * @return 影响行数 */ public<T> int merge(T entity){ try { var sp = updateSqlAndParams(entity); return executeUpdate(sp.getSql(),sp.getParams()); } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) { e.printStackTrace(); } return 0; } /** * 根据主键(id)获取对应的实体信息 * @param id id * @param clazz 具体类型 * @return */ public<T> T getById(Serializable id,Class<T> clazz){ var builder = new StringBuilder("SELECT "); try { var pds = Introspector.getBeanInfo(clazz,Object.class).getPropertyDescriptors(); for(var pd : pds){ var method = pd.getReadMethod(); if(method.getAnnotation(Id.class) != null){ builder.append(method.getAnnotation(Id.class).value()).append(','); }else{ builder.append(method.getAnnotation(Column.class) == null ? pd.getName() : method.getAnnotation(Column.class).value()).append(','); } } var sql = builder.deleteCharAt(builder.length() - 1).append(" FROM ").append(getTableName(clazz)).append(" WHERE ").append(getIdName(clazz)).append(" = ?").toString(); return executeQuery(sql,new BeanHandler<>(clazz),id); } catch (IntrospectionException e) { e.printStackTrace(); } return null; } /** * sql语句以及站位参数值 */ private class SqlAndParams{ //sql语句 private String sql; //站位参数?对应的值 private Object[] params; public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } public Object[] getParams() { return params; } public void setParams(Object[] params) { this.params = params; } } /** * 生成具体实体entity的插入sql预编译语句以及?占位符参数值 * INSERT INTO tb_name(fields...) VALUES(?,....?) * @param entity 具体的javaBean实体 * @param <T> 具体的类型 * @return SqlAndParams */ private<T> SqlAndParams insertSqlAndParams(T entity) throws IntrospectionException,InvocationTargetException,IllegalAccessException { var sp = new SqlAndParams(); var params = new ArrayList<>(); var builder = new StringBuilder("INSERT INTO "); builder.append(getTableName(entity.getClass())).append('('); var beanInfo = Introspector.getBeanInfo(entity.getClass(),Object.class); var pds = beanInfo.getPropertyDescriptors(); for(var pd : pds){ //排除掉id的拼接 var method = pd.getReadMethod(); if(method.getAnnotation(Id.class) != null)continue; //获取属性对应的属性名,属性名默认对应数据库里的字段名(非默认需处理) var column = method.getAnnotation(Column.class); var name = column != null ? column.value() : pd.getName(); builder.append(name).append(','); } builder.deleteCharAt(builder.length() - 1);//删除最后多余的, builder.append(") VALUES("); for(int i = 0; i < pds.length; i++){ //获取该属性对应的getter var method = pds[i].getReadMethod(); //排除掉id的设置 if(method.getAnnotation(Id.class) != null)continue; builder.append("?,"); params.add(method.invoke(entity)); } builder.deleteCharAt(builder.length() - 1).append(')'); sp.setSql(builder.toString()); sp.setParams(params.toArray()); return sp; } /** * 获取指定表名 Book/User/Topic/Reply... * @param clazz 实体类型 * @return 表名,默认为类(类型)的简单名称 * 非默认需要额外处理: * 简单名称 数据库表名 * Book tb_book */ private String getTableName(Class<?> clazz) { /* var tableName = clazz.getSimpleName(); *//* * 处理表名和类的简单名称不一致的情况 *//* var table = clazz.getAnnotation(Table.class); if(table != null) tableName = table.value();*/ return clazz.getAnnotation(Table.class) == null ? clazz.getSimpleName() : clazz.getAnnotation(Table.class).value(); } /** * 获取id名称 * @param clazz 类型 * @return id名称 */ private String getIdName(Class<?> clazz){ String idName = "id"; try { var pds = Introspector.getBeanInfo(clazz,Object.class).getPropertyDescriptors(); for(var pd : pds){ var id = pd.getReadMethod().getAnnotation(Id.class); if(id != null) return id.value(); } } catch (IntrospectionException e) { e.printStackTrace(); } return idName; } /** * 更新的sql语句及预编译参数占位符值 * @param entity 更新后的实体信息 * @param <T> 类型 * @return * @throws IntrospectionException * @throws InvocationTargetException * @throws IllegalAccessException */ private<T> SqlAndParams updateSqlAndParams(T entity) throws IntrospectionException,InvocationTargetException,IllegalAccessException { var sp = new SqlAndParams(); var params = new ArrayList<>(); var builder = new StringBuilder("UPDATE "); builder.append(getTableName(entity.getClass())).append(" SET "); var beanInfo = Introspector.getBeanInfo(entity.getClass(),Object.class); var pds = beanInfo.getPropertyDescriptors(); Object idValue = null; for(var pd : pds){ //排除掉id的拼接 var method = pd.getReadMethod(); if(method.getAnnotation(Id.class) != null) idValue = method.invoke(entity); else { //获取属性对应的属性名,属性名默认对应数据库里的字段名(非默认需处理) var column = method.getAnnotation(Column.class); var name = column != null ? column.value() : pd.getName(); builder.append(name).append(" = ?,"); params.add(method.invoke(entity)); } } builder.deleteCharAt(builder.length() - 1);//删除最后多余的, builder.append(" WHERE ").append(getIdName(entity.getClass())).append(" = ?"); params.add(idValue); sp.setSql(builder.toString()); sp.setParams(params.toArray()); return sp; } }
对结果集的处理参考前一篇博客里的策略模式,接下来添加缓存功能以及事务功能
//缓存池 private Map<String, Object> cache = new HashMap<>(); //可重入读写锁 private ReadWriteLock rwl = new ReentrantReadWriteLock(); /** * 添加实体数据到数据库 * @param entity 实体信息 * @return 影响行数,后期可以返回自增的主键 */ public <T> int insert(T entity){ var ret = 0; //生成具体的插入的sql语句 try { var sp = insertSqlAndParams(entity); System.out.println(sp.getSql()); System.out.println(Arrays.toString(sp.getParams())); ret = executeUpdate(sp.getSql(),sp.getParams()); var key = entity.getClass().getName() + "_" + getIdName(entity.getClass()); //将插入的数据添加进缓存池中 rwl.writeLock().lock(); try{ cache.put(key,entity); }finally{ rwl.writeLock().unlock(); } } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) { e.printStackTrace(); } return ret; } /** * 根据主键primary key(id)删除指定数据 * @param id 主键id * @return 影响行数 */ public int delete(Serializable id,Class<?> clazz){ var sql = "DELETE FROM " + getTableName(clazz) + " WHERE " + getIdName(clazz) + " = ?"; var ret = executeUpdate(sql,id); var key = clazz.getName() + "_" + id; //从缓存中移出删除的对象 rwl.writeLock().lock(); try{ cache.remove(key); }finally{ rwl.writeLock().unlock(); } return ret; } /** * 根据主键(id)修改对应的信息 * @param entity 修改好的实体信息 * @return 影响行数 */ public<T> int merge(T entity){ var ret = 0; try { var sp = updateSqlAndParams(entity); ret = executeUpdate(sp.getSql(),sp.getParams()); rwl.writeLock().lock(); try{ cache.replace(entity.getClass().getName() + "_" + getIdName(entity.getClass()),entity); }finally{ rwl.writeLock().unlock(); } } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) { e.printStackTrace(); } return ret; } /** * 根据主键(id)获取对应的实体信息,先从缓存中去加载,没有找到再到数据库中去加载 * @param id id * @param clazz 具体类型 * @return */ public<T> T getById(Serializable id,Class<T> clazz){ var key = clazz.getName() + "_" +id; rwl.readLock().lock(); Object value = null; try{ value = cache.get(key); if(value == null){ rwl.readLock().unlock(); rwl.writeLock().lock(); try{ if(value==null){ value = queryDB(id,clazz); if(value != null) cache.put(key,value); } }finally{ rwl.writeLock().unlock(); } rwl.readLock().lock(); } }finally{ rwl.readLock().unlock(); } return (T)value; } /** * 根据主键(id)获取对应的实体信息 * @param id id * @param clazz 具体类型 * @return */ private <T> T queryDB(Serializable id,Class<T> clazz){ var builder = new StringBuilder("SELECT "); try { var pds = Introspector.getBeanInfo(clazz,Object.class).getPropertyDescriptors(); for(var pd : pds){ var method = pd.getReadMethod(); if(method.getAnnotation(Id.class) != null){ builder.append(method.getAnnotation(Id.class).value()).append(','); }else{ builder.append(method.getAnnotation(Column.class) == null ? pd.getName() : method.getAnnotation(Column.class).value()).append(','); } } var sql = builder.deleteCharAt(builder.length() - 1).append(" FROM ").append(getTableName(clazz)).append(" WHERE ").append(getIdName(clazz)).append(" = ?").toString(); return executeQuery(sql,new BeanHandler<>(clazz),id); } catch (IntrospectionException e) { e.printStackTrace(); return null; } } public<T> List<T> list(Class<T> clazz,int offset, int size){ List<T> list = null; var builder = new StringBuilder("SELECT "); try { var pds = Introspector.getBeanInfo(clazz, Object.class).getPropertyDescriptors(); for (var pd : pds) { var method = pd.getReadMethod(); if (method.getAnnotation(Id.class) != null) { builder.append(method.getAnnotation(Id.class).value()).append(','); } else { builder.append(method.getAnnotation(Column.class) == null ? pd.getName() : method.getAnnotation(Column.class).value()).append(','); } } builder.deleteCharAt(builder.length() - 1).append(" FROM ").append(getTableName(clazz)); if(offset > 0 && size > 1) { builder.append(" LIMIT ?,?"); list = this.executeQuery(builder.toString(),new ListBeanHandler<>(clazz),offset,size); }else{ list = this.executeQuery(builder.toString(),new ListBeanHandler<>(clazz)); } }catch (IntrospectionException e){ e.printStackTrace(); } return list; } public<T> Long getCount(Class<T> clazz){ var sql = "SELECT COUNT(1) FROM " + getTableName(clazz); return this.executeQuery(sql,rs -> rs.next() ? rs.getLong(1) : 0); }
提供一个SqlSessionFactory工具类用来创建SqlSession对象,简单使用
public class BookDaoImpl implements BookDao { private SqlSession template = SqlSession.buildSession(); @Override public void persistent(Book book) { template.insert(book); } @Override public void delete(Integer id) { template.delete(id,Book.class); } @Override public void update(Book book) { template.merge(book); } @Override public Book search(Integer id) { return template.getById(id,Book.class); } @Override public List<Book> search() { return template.list(Book.class,-1,-1); } @Override public long getCount() { return template.getCount(); } }
相关推荐
《简易数据库关系映射框架EasyDBO v0.1.0 测试版》是一个专为JSP和JAVA语言开发的毕业设计项目,旨在提供一种简便的数据库操作方式,减轻开发者在处理数据层时的负担。EasyDBO是数据库对象(DBO)的简称,它是一个轻...
- 对Hibernate等ORM框架的配置和控制感到繁琐的开发者。 - 认为直接编写SQL语句过于麻烦的开发人员。 - 希望简化Spring配置文件管理的团队。 - 需要简化XML配置文件编写或偏好可视化编辑工具的开发者。 - 寻找简单...
【简易数据库关系映射框架EasyDBO v0.1.0 测试版】是一个针对Java JSP应用的源码示例,主要用于帮助学生进行毕业设计和论文研究。这个框架简化了数据库操作,使得开发者能够更加便捷地处理数据交互。在Java世界中,...
**Hibernate** 是一个对象关系映射(ORM)框架,它允许开发者用Java对象来操作数据库记录,而无需关心底层SQL语句。Hibernate通过映射XML文件或注解,自动将Java对象转化为数据库中的记录,大大减少了手动编写SQL的...
《构建简易JavaEE博客系统》 在信息技术领域,JavaEE(Java Platform, Enterprise Edition)是一种广泛应用于企业级应用开发的平台,它提供了丰富的组件和服务,用于构建可扩展、安全且高效的分布式应用程序。本...
4. **数据持久化**:银行系统的数据通常需要持久化存储,可能采用了Java的文件系统操作、JDBC(Java Database Connectivity)连接数据库,或者更高级的ORM框架如Hibernate或MyBatis。 5. **用户界面**:BAM可能包含...
另外,Hibernate作为一种ORM(Object-Relational Mapping)框架,也可能被用来简化数据库操作,将Java对象与数据库表自动映射,减少手动编写SQL的负担。 项目可能还使用了JavaScript和jQuery库在前端实现动态效果和...
7. **数据库操作**:可能使用JDBC进行数据库连接和查询,也可能使用ORM框架如Hibernate进行对象关系映射。 8. **安全性**:如会话管理、参数验证、防止SQL注入和跨站脚本攻击等。 通过学习和分析这个项目,开发者...
4. **数据持久化**:可能使用ORM(对象关系映射)框架如Hibernate或MyBatis,以简化数据库操作,将Java对象和数据库表映射起来。 5. **Swing或JavaFX**:作为用户界面,项目可能使用Swing或JavaFX库来构建图形用户...
数据库操作通常会通过JDBC(Java Database Connectivity)或者ORM(对象关系映射)框架如Hibernate或MyBatis来实现,这些工具能够将Java对象与数据库中的数据进行映射,简化数据交互。 页面展示部分,可能会用到...
项目可能使用JDBC(Java Database Connectivity)或ORM(Object-Relational Mapping)框架如Hibernate,来连接和操作数据库。 6. **用户认证与授权**:博客系统可能包含用户注册、登录等功能,这涉及到身份验证和...