`
Adan-Chiu
  • 浏览: 21790 次
社区版块
存档分类
最新评论

封装jdbc-完成简易版的orm框架

    博客分类:
  • jdbc
 
阅读更多

      前面我们使用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 测试版_easydbo010.rar

    《简易数据库关系映射框架EasyDBO v0.1.0 测试版》是一个专为JSP和JAVA语言开发的毕业设计项目,旨在提供一种简便的数据库操作方式,减轻开发者在处理数据层时的负担。EasyDBO是数据库对象(DBO)的简称,它是一个轻...

    [其他类别]简易数据库关系映射框架EasyDBO v0.1.0 测试版_easydbo010.zip

    【简易数据库关系映射框架EasyDBO v0.1.0 测试版】是一个针对Java JSP应用的源码示例,主要用于帮助学生进行毕业设计和论文研究。这个框架简化了数据库操作,使得开发者能够更加便捷地处理数据交互。在Java世界中,...

    基于ssh的简易项目

    **Hibernate** 是一个对象关系映射(ORM)框架,它允许开发者用Java对象来操作数据库记录,而无需关心底层SQL语句。Hibernate通过映射XML文件或注解,自动将Java对象转化为数据库中的记录,大大减少了手动编写SQL的...

    A Simple JavaEE Blog.zip

    《构建简易JavaEE博客系统》 在信息技术领域,JavaEE(Java Platform, Enterprise Edition)是一种广泛应用于企业级应用开发的平台,它提供了丰富的组件和服务,用于构建可扩展、安全且高效的分布式应用程序。本...

    BAM java编的一个简单的银行系统

    4. **数据持久化**:银行系统的数据通常需要持久化存储,可能采用了Java的文件系统操作、JDBC(Java Database Connectivity)连接数据库,或者更高级的ORM框架如Hibernate或MyBatis。 5. **用户界面**:BAM可能包含...

    基于java web的超市管理系统(包含源文件)

    另外,Hibernate作为一种ORM(Object-Relational Mapping)框架,也可能被用来简化数据库操作,将Java对象与数据库表自动映射,减少手动编写SQL的负担。 项目可能还使用了JavaScript和jQuery库在前端实现动态效果和...

    完整的JSP网站书店项目 v1.5

    7. **数据库操作**:可能使用JDBC进行数据库连接和查询,也可能使用ORM框架如Hibernate进行对象关系映射。 8. **安全性**:如会话管理、参数验证、防止SQL注入和跨站脚本攻击等。 通过学习和分析这个项目,开发者...

    java练手小项目

    4. **数据持久化**:可能使用ORM(对象关系映射)框架如Hibernate或MyBatis,以简化数据库操作,将Java对象和数据库表映射起来。 5. **Swing或JavaFX**:作为用户界面,项目可能使用Swing或JavaFX库来构建图形用户...

    云笔记资源代码

    数据库操作通常会通过JDBC(Java Database Connectivity)或者ORM(对象关系映射)框架如Hibernate或MyBatis来实现,这些工具能够将Java对象与数据库中的数据进行映射,简化数据交互。 页面展示部分,可能会用到...

    jsp + javabean个人博客

    项目可能使用JDBC(Java Database Connectivity)或ORM(Object-Relational Mapping)框架如Hibernate,来连接和操作数据库。 6. **用户认证与授权**:博客系统可能包含用户注册、登录等功能,这涉及到身份验证和...

Global site tag (gtag.js) - Google Analytics