前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >半个小时手写一个极简版ORM框架,实现简单的CRUD操作

半个小时手写一个极简版ORM框架,实现简单的CRUD操作

作者头像
小四的技术之旅
发布2022-07-26 17:11:21
7700
发布2022-07-26 17:11:21
举报
文章被收录于专栏:小四的技术文章

码农在囧途

已经忘记上一次写字是什么时候了,应该很久了吧,突然间想写写字,我翻箱倒柜的找了找,只找到了笔,却没有本, 这笔中的墨虽然不足以支撑我书写糟糕的过去和未知的未来,却能写下我当下的能把握住的人生,即使没有运笔舒畅的纸张, 床边的窗帘也足够我草草了事,虽写不出丹麦王子的悲剧,子美诗篇的荡气回肠,不过城市的灯光洒落在破旧的窗帘上, 夹缝中的几个字却是人生坚持的理想!

前言

ORM框架可以减轻在开发中的一些负担,简单的单表的增删改查如果全部都写sql的话那么也会是一个工作量,因为不仅要面临写大量的sql语句, 还要处理jdbc结果集映射到实体的操作,这其中会面临写大量重复无用的代码,而且在结果集映射的过程中出错的可能性也很大,所以就出现了 很多ORM框架,例如Mybatis,Hibernate等,对于简单的单表的操作,这些框架提供了大量的API给我们使用,大大的减轻开发的负担,本文 就实现一个简单版的ORM框架,让大家理解ORM的实现思路。

工程结构

代码语言:javascript
复制
└─com
    └─steak
        └─orm
            │  SimpleOrmApplication.java
            │  
            ├─annotation
            │      PrimaryKey.java
            │      Table.java
            │      
            ├─builder
            │      BaseSQLBuilder.java
            │      DeleteSQLBuilder.java
            │      QuerySQLBuilder.java
            │      SaveSQLBuilder.java
            │      UpdateSQLBuilder.java
            │      
            ├─datasource
            │      JdbcTemplate.java
            │      MyDataSource.java
            │      RowMapper.java
            │      
            ├─domain
            │      User.java
            │      
            ├─factory
            │      SQLBuilderInstanceFactory.java
            │      
            ├─service
            │  │  IDelete.java
            │  │  IQuery.java
            │  │  ISave.java
            │  │  IUpdate.java
            │  │  
            │  └─impl
            │          Delete.java
            │          Query.java
            │          Save.java
            │          Update.java
            │          
            ├─test
            │      Client.java     
            └─tool
                    StringUtil.java

我们要做的就是实现实体到数据库字段的映射,那么势必会涉及到大量的反射操作,增删改查的操作都是一样的,只是查询多了一个结果集 的映射,增删改没有,所以他们在构建sql语句的时候其实都是一样的,那么本文就只来讲解查询功能,其他的就不讲了。

编码实现

Table注解

@Table注解标注在实体上面,表明是一个DO,在领域驱动设计中,对于实体的划分是严格的,但是在平常的开发中,我们发现开发人员对于实体的划分 是不严格的,比如DO应该是和数据库中的字段是一一对应的,这个实体的职责就是和数据库字段的映射,不应该有其他的职责,所以里面不应该添加其他的 字段,但是很多时候我们看到的是,这个实体中充满了很多额外字段,这个实体不仅作为数据传输对象DTO,还作为了视图对象VO,这都是不规范的做法。 @Table注解中value用来指定数据表名称,如果不指定,则默认为实体类名的小写。

代码语言:javascript
复制
/**
 * @author 刘牌
 * @date 2022/3/315:34
 */
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Table {
    String value() default "";
}

主键注解PrimaryKey

PrimaryKey的作用是标明那个字段是主键,如果value值为空,则m默认使用字段名。

代码语言:javascript
复制
/**
 * @author 刘牌
 * @date 2022/3/414:26
 */
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PrimaryKey {
    String value() default "";
}

IQuery接口

定义一个查询接口,参数是一个泛型参数

代码语言:javascript
复制
/**
 * @author 刘牌
 * @date 2022/3/322:31
 */
public interface IQuery<T> {
    List<T> query(T t) throws Exception;
}

查询实现类Query

Query类相当于一个执行器,它会调用sql构造器构造sql,然后调用Jdbc执行sql语句,并返回结果,它继承自JdbcTemplate,JdbcTemplate 封装了jdbc的连接和CRUD操作,Query类中核心的是调用sql构造器,和jdbc结果集和实体之间的映射,二者都是利用反射操作来完成的。

代码语言:javascript
复制
/**
 * @author 刘牌
 * @date 2022/3/322:35
 */
public class Query<T> extends JdbcTemplate implements IQuery<T> {

    @Override
    public List<T> query(T t) throws Exception {
        String sql = SQLBuilderInstanceFactory.getQueryBuilder().querySql(t);
        Field[] fields = t.getClass().getDeclaredFields();
        return executeQuery(sql, new RowMapper<T>() {
            @Override
            public T mapRow(ResultSet resultSet) throws Exception {
                for (Field field : fields) {
                    String getField = StringUtil.getSetMethod(StringUtil.getLastStr(field.toString()));
                    Object object = resultSet.getObject(StringUtil.getLastStr(field.toString()), field.getType());
                    t.getClass().getMethod(getField,field.getType()).invoke(t,object);
                }
                return t;
            }
        });
    }
}

sql构建基类BaseSQLBuilder

BaseSQLBuilder主要提取出一些工作的常量和方法,

代码语言:javascript
复制
/**
 * @author 刘牌
 * @date 2022/3/413:58
 */
public abstract class BaseSQLBuilder {

    protected String tableName; //表名
    protected String primaryKeyName; //主键名
    protected final String SELECT = "select ";
    protected final String FROM = " from ";
    protected final String WHERE = " where ";
    protected final String AND = " and ";
    protected final String IN = " IN ";
    protected final String UPDATE = " UPDATE ";
    protected final String SET = " SET ";
    protected final String VALUES = " VALUES ";
    protected final String OR = " OR ";
    protected final String DELETE = " DELETE ";
    protected final String INSERT = " INSERT ";
    protected final String INTO = " INTO ";

    protected StringBuilder sqlBuilder = new StringBuilder();

    //获取表名
    protected void getTableName(Object obj){
        Table table = obj.getClass().getAnnotation(Table.class);
        tableName = table.value();
        if (Objects.equals(tableName, ""))
            tableName = StringUtil.getLastStr(obj.getClass().getName());
    }
    //获取主键名
    protected void getPrimaryKey(Field field){
        PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
        primaryKeyName = primaryKey.value();
        if (Objects.equals(primaryKeyName, ""))
            primaryKeyName = StringUtil.getLastStr(field.getClass().getName());
    }
    //判断主键
    protected boolean hasPrimaryKey(Field field){
        PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
        return primaryKey != null;
    }
    
    protected String getField(String fieldStr){
        return "get" + fieldStr.substring(0, 1).toUpperCase() + fieldStr.substring(1);
    }

    protected Field[] getFields(Object obj){
        return obj.getClass().getDeclaredFields();
    }
}

查询sql构建类QuerySQLBuilder

它的作用就是构建查询sql,通过反射操作,实现sql的动态拼接。

代码语言:javascript
复制
/**
 * @author 刘牌
 * @date 2022/3/315:38
 */
public class QuerySQLBuilder extends BaseSQLBuilder {
    public String querySql(Object t) throws Exception {
        getTableName(t);
        sqlBuilder.append(SELECT + "*" + FROM).append(tableName).append(WHERE + " 1=1 ");
        for (Field field : getFields(t)) {
            String fieldStr = StringUtil.getLastStr(field.toString());
            Object value = t.getClass().getMethod(getField(fieldStr)).invoke(t);
            if (!"".equals(value) && null != value) {
                sqlBuilder.append(AND).append(fieldStr).append("=").append("'").append(value).append("'");
            }
        }
        return sqlBuilder.toString();
    }
}

执行sql语句

JdbcTemplate中查询操作。

代码语言:javascript
复制
public abstract class JdbcTemplate extends MyDataSource {

    protected static Connection connection;
    protected static PreparedStatement preparedStatement;
    protected ResultSet resultSet;

    //查询
    protected <T> List<T> executeQuery(String sql, RowMapper<T> rowMapper) throws Exception {
        preparedStatement = preparedStatement(sql);
        resultSet = preparedStatement.executeQuery();
        List<T> list = resultSet(resultSet, rowMapper);
        close();
        return list;
    }

    //结果集
    private <T> List<T> resultSet(ResultSet resultSet, RowMapper<T> rowMapper) throws Exception {
        List<T> list = new ArrayList<>();
        while (resultSet.next()) {
            list.add(rowMapper.mapRow(resultSet));
        }
        return list;
    }
}

测试

我们只构造一个实体作为查询条件,就能够查询出对应的数据。

代码语言:javascript
复制
/**
 * @author 刘牌
 * @date 2022/3/315:46
 */
public class Client {
    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setAddress("china");
        user.setUsername("steak");
        user.setGender("man");
        user.setId(3);
        List<User> userList = new Query<User>().query(user);
        System.out.println(userList);
    }
}

查询功能就完成了,其他的添加,删除,修改等功能其实也是一个的思想,都是构建sql,执行查询操作,在本示例中,例子过于简单,显然不能够满足 开发需要,只提供了条件查询操作,并没有提供像去重,分组,排序等等操作,不过这些要加入这些操作,其实也是一样的,只要我们明白其核心思想就行了, 在实际开发中我们也不会自己去封装一套,因为像Mybatis这种ORM框架提供更加方便快捷友好的操作,完全能够满足我们的需求,我们造轮子的初心并不是 取代别的轮子,而是在造轮子的过程中提升自己的认知和水平。

项目demo地址

对于其他的增加,删除修改操作,可去看源代码。

https://gitee.com/steakliu/design-pattern/tree/de623f1dc97e8793fa732e726f88c480132288c8/orm/simpleOrm

今天的分享就到这里,感谢你的观看,下期见。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 刘牌 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 工程结构
  • 编码实现
    • Table注解
      • 主键注解PrimaryKey
        • IQuery接口
          • 查询实现类Query
            • sql构建基类BaseSQLBuilder
              • 查询sql构建类QuerySQLBuilder
                • 执行sql语句
                  • 测试
                    • 项目demo地址
                    相关产品与服务
                    数据库
                    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档