码农在囧途
已经忘记上一次写字是什么时候了,应该很久了吧,突然间想写写字,我翻箱倒柜的找了找,只找到了笔,却没有本, 这笔中的墨虽然不足以支撑我书写糟糕的过去和未知的未来,却能写下我当下的能把握住的人生,即使没有运笔舒畅的纸张, 床边的窗帘也足够我草草了事,虽写不出丹麦王子的悲剧,子美诗篇的荡气回肠,不过城市的灯光洒落在破旧的窗帘上, 夹缝中的几个字却是人生坚持的理想!
前言
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结果集和实体之间的映射,二者都是利用反射操作来完成的。
/**
* @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的动态拼接。
代码语言: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
中查询操作。
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
今天的分享就到这里,感谢你的观看,下期见。