一个最简单的ORM总结下来就两部分:
* 根据entity上的自定义注解生成mapping元数据信息
* 生成mapper接口的动态代理,根据具体的方法,动态生成sql并执行sql,然后通过反射的方式映射到具体的实体对象上去
解析自定义注解,生成元数据信息
首先我们先定义几个元信息注解:
@Table
注解在entity类上,标注这个实体类对应的数据库的表名
/**
* TODO
*
* @author : xiaojun
* @since 10:57 2018/11/19
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
/**
* <p>
* 实体对应的表名
* </p>
*/
String value() default "";
}
@Column
标注在实体类的属性上,用来和表的字段名进行映射
package com.example.mybatisplusdemo.orm.annotation;
import java.lang.annotation.*;
/**
* 数据库表的列名
*
* @author : xiaojun
* @since 10:58 2018/11/19
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
String value() default "";
}
@PK
用来标注表的主键字段
package com.example.mybatisplusdemo.orm.annotation;
import java.lang.annotation.*;
/**
* 主键标识
*
* @author : xiaojun
* @since 11:02 2018/11/19
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface PK {
}
- 以下是一个具体的User entity demo
package com.example.mybatisplusdemo.orm.entity;
import com.example.mybatisplusdemo.orm.annotation.Column;
import com.example.mybatisplusdemo.orm.annotation.PK;
import com.example.mybatisplusdemo.orm.annotation.Table;
import java.sql.Timestamp;
/**
* <p>
* 系统用户
* </p>
*
* @author xiaojun
* @since 2018-11-16
*/
@Table("sys_user")
public class User {
@PK
@Column("user_id")
private Long userId;
@Column("username")
private String username;
@Column("password")
private String password;
@Column("salt")
private String salt;
@Column("email")
private String email;
@Column("mobile")
private String mobile;
@Column("status")
private Integer status;
@Column("dept_id")
private Long deptId;
@Column("create_time")
private Timestamp createTime;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getDeptId() {
return deptId;
}
public void setDeptId(Long deptId) {
this.deptId = deptId;
}
public Timestamp getCreateTime() {
return createTime;
}
public void setCreateTime(Timestamp createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", username=" + username +
", password=" + password +
", salt=" + salt +
", email=" + email +
", mobile=" + mobile +
", status=" + status +
", deptId=" + deptId +
", createTime=" + createTime +
"}";
}
}
- 接下来我们定义一个通用Mapper,泛型T为具体的Entity实体类,作为demo示例,我们就只创建一个
selectById
方法
package com.example.mybatisplusdemo.orm.mapper;
/**
* base mapper
*
* @author : xiaojun
* @since 11:05 2018/11/19
*/
public interface BaseMapper<T> {
T selectById(Object id);
//其他删除\修改\新增同理,demo目的忽略
}
- 然后我们定义一个
UserMapper
继承BaseMapper<User>
,User
为上面定义的用户实体类
package com.example.mybatisplusdemo.orm.mapper;
import com.example.mybatisplusdemo.orm.entity.User;
/**
* user mapper
*
* @author : xiaojun
* @since 11:04 2018/11/19
*/
public interface UserMapper extends BaseMapper<User> {
}
- 构建元数据信息,主要有表元数据信息
TableMetadata
和列元数据信息ColumnMetadata
package com.example.mybatisplusdemo.orm.metadata;
import com.example.mybatisplusdemo.orm.annotation.Column;
import com.example.mybatisplusdemo.orm.annotation.PK;
import com.example.mybatisplusdemo.orm.annotation.Table;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 表元数据信息
*
* @author : xiaojun
* @since 11:51 2018/11/19
*/
public class TableMetadata {
private String tableName;
private ColumnMetadata primaryKey;
private List<ColumnMetadata> columns = new ArrayList<>();
private String selectByIdSql;
private Class entityClass;
/**
* 解析Mapper类构建表元数据信息
*
* @param mapperClass 具体的Mapper类入UserMapper
*/
public TableMetadata(Class<?> mapperClass) {
ResolvableType resolvableType = ResolvableType.forClass(mapperClass);
ResolvableType baseMapperType = resolvableType.getInterfaces()[0];
//获得泛型父接口的具体泛型类型,这里是具体的Entity类
this.entityClass = baseMapperType.resolveGeneric(0);
//获取实体类上注解的数据库表名
Table tableName = AnnotationUtils.getAnnotation(entityClass, Table.class);
this.tableName = tableName.value();
//获取实体类属性上所有注解了@Column的属性的数据库表字段名
for (int i = 0; i < entityClass.getDeclaredFields().length; i++) {
ColumnMetadata columnMetadata = new ColumnMetadata();
Field field = entityClass.getDeclaredFields()[i];
//获取列名注解
Column column = AnnotationUtils.getAnnotation(field, Column.class);
if (column != null) {
columnMetadata.setColumnIndex(i + 1);
columnMetadata.setColumnName(column.value());
ResolvableType columnResolvableType = ResolvableType.forField(field);
columnMetadata.setJavaType(columnResolvableType.resolve());
columnMetadata.setField(field);
//解析主键字段
PK pk = AnnotationUtils.getAnnotation(field, PK.class);
if (pk != null) {
primaryKey = columnMetadata;
}
columns.add(columnMetadata);
}
}
this.selectByIdSql = genSelectByIdSql();
}
public Class getEntityClass() {
return entityClass;
}
public void setEntityClass(Class entityClass) {
this.entityClass = entityClass;
}
public String getSelectByIdSql() {
return selectByIdSql;
}
public void setSelectByIdSql(String selectByIdSql) {
this.selectByIdSql = selectByIdSql;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public ColumnMetadata getPrimaryKey() {
return primaryKey;
}
public void setPrimaryKey(ColumnMetadata primaryKey) {
this.primaryKey = primaryKey;
}
public List<ColumnMetadata> getColumns() {
return columns;
}
public void setColumns(List<ColumnMetadata> columns) {
this.columns = columns;
}
/**
* 生成一个select <columns> from <table> where pk=? 查询语句
*
* @return SelectByIdSql
*/
public String genSelectByIdSql() {
StringBuilder selectByIdBindSql = new StringBuilder("select ")
.append(StringUtils.collectionToCommaDelimitedString(columns.stream().map(ColumnMetadata::getColumnName).collect(Collectors.toList())))
.append(" from ").append(tableName).append(" where ").append(primaryKey.getColumnName() + " = ?");
return selectByIdBindSql.toString();
}
}
package com.example.mybatisplusdemo.orm.metadata;
import java.lang.reflect.Field;
/**
* 列元数据信息
*
* @author : xiaojun
* @since 11:42 2018/11/19
*/
public class ColumnMetadata {
/**
* 列名
*/
private String columnName;
/**
* 列序号
*/
private int columnIndex;
/**
* 对应的java类型
*/
private Class<?> javaType;
/**
* Entity类的属性字段,用来反射设置值
*/
private Field field;
public Field getField() {
return field;
}
public void setField(Field field) {
this.field = field;
}
public Class<?> getJavaType() {
return javaType;
}
public void setJavaType(Class<?> javaType) {
this.javaType = javaType;
}
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public int getColumnIndex() {
return columnIndex;
}
public void setColumnIndex(int columnIndex) {
this.columnIndex = columnIndex;
}
}
这里不得不提一下spring core提供的
org.springframework.core.ResolvableType
类,帮助我们简化了解析类元数据信息的工作,还有各种的util类如org.springframework.core.annotation.AnnotationUtils
,org.springframework.core.annotation.AnnotatedElementUtils
等等
生成一个动态代理
- 通过jdk提供的
Proxy
类来生成动态代理
package com.example.mybatisplusdemo.orm.proxy;
import com.example.mybatisplusdemo.orm.metadata.ColumnMetadata;
import com.example.mybatisplusdemo.orm.metadata.TableMetadata;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* mapper 动态代理类
*
* @author : xiaojun
* @since 11:29 2018/11/19
*/
public class MapperProxy implements InvocationHandler {
private final String url = "jdbc:mysql://localhost:3306/test?Unicode=true&characterEncoding=UTF-8&autoReconnect=true&connectTimeout=60000&socketTimeout=60000&useInformationSchema=true";
private final String username = "root";
private final String pwd = "111";
private Class<?> mapperClass;
private TableMetadata tableMetadata;
public MapperProxy(Class<?> mapperClass) {
this.mapperClass = mapperClass;
this.tableMetadata = new TableMetadata(mapperClass);
}
public static <T> T getMapper(Class<T> mapperClass) {
return (T) Proxy.newProxyInstance(MapperProxy.class.getClassLoader(), new Class[]{mapperClass}, new MapperProxy(mapperClass));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try (Connection connection = DriverManager.getConnection(url, username, pwd)) {
//这里demo简化直接创建数据库连接,然后实现selectById方法
if ("selectById".equals(method.getName())) {
String selectByIdSql = tableMetadata.getSelectByIdSql();
try (PreparedStatement preparedStatement = connection.prepareStatement(selectByIdSql)) {
preparedStatement.setObject(1, args[0]);
try (ResultSet resultSet = preparedStatement.executeQuery()) {
//反射创建实体类
Object entity = tableMetadata.getEntityClass().newInstance();
if (resultSet.next()) {
for (ColumnMetadata columnMetadata : tableMetadata.getColumns()) {
//反射设置属性
columnMetadata.getField().setAccessible(true);
columnMetadata.getField().set(entity, resultSet.getObject(columnMetadata.getColumnIndex()));
}
return entity;
}
}
}
}
}
return null;
}
}
最后,我们测试一下功能
package com.example.mybatisplusdemo;
import com.example.mybatisplusdemo.orm.entity.User;
import com.example.mybatisplusdemo.orm.mapper.UserMapper;
import com.example.mybatisplusdemo.orm.proxy.MapperProxy;
/**
* 测试 orm
*
* @author : xiaojun
* @since 13:34 2018/11/19
*/
public class TestOrm {
public static void main(String[] args) {
UserMapper userMapper = MapperProxy.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
System.out.println(user);
}
}
正确输出了数据库中的记录:
User{userId=2, username=xiaojun, password=659d0db693e9f694ee53b0a96c3e4db87e0e8a23fa039064dd444bcc9d7950a6, salt=zZRlJqdSlqBmsMK8oic6, email=xiaoj@xx.com, mobile=13311111111, status=1, deptId=7, createTime=2018-11-16 15:10:31.0}
我们的简单orm框架生效了,对于使用者来说是透明的,怎么去查询,然后封装成实体对象,全部交由orm框架来完成,仅仅是调用了BaseMapper.selectById
方法,然后就获得了User
实体对象
当然这里只是最简单的demo而已,阐述的是一个核心原理,看客们可以自己发散思维重构完善代码