前面我们使用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";
}
实体元数据的定义
Java代码
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();
}
}