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

喜欢可以关注本公众号,一起在学习中成长,在成长中学习!

码农在囧途

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

前言

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

工程结构

└─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用来指定数据表名称,如果不指定,则默认为实体类名的小写。

/**
* @author 刘牌
* @date 2022/3/315:34
*/

@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Table {
String value() default "";
}

主键注解PrimaryKey

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

/**
* @author 刘牌
* @date 2022/3/414:26
*/

@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PrimaryKey {
String value() default "";
}

IQuery接口

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

/**
* @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结果集和实体之间的映射,二者都是利用反射操作来完成的。

/**
* @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主要提取出一些工作的常量和方法,

/**
* @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的动态拼接。

/**
* @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中查询操作。

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;
}
}

测试

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

/**
* @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);
}
}


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

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

项目demo地址

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

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

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


原文始发于微信公众号(刘牌):半个小时手写一个极简版ORM框架,实现简单的CRUD操作

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/150817.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!