封裝JDBC-完成簡易版的ORM框架
設計簡易版的ORM映射框架:對外隱藏jdbc的實現細節,對方法api調用者來說只需要以操作對象的方式來調用就可以了。
默認映射規則
沒有特殊說明,類的簡單名稱對應關係數據庫的表明,如果不一致,我們自定義一個註解@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();
}
}