从Spring data jpa看Mybatis, 实现自己的JpaMapper

2023-10-22 15:14:30 浏览数 (2)

从Spring data jpa看Mybatis, 实现自己的JpaMapper

一、Spring data jpa

1.1 Spring data jpa概述

JPA是Java Persistence API的简称,中文名Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。

Jpa是一个标准,就像JTA、JMS这种一样。Java Persistence API里面定义了一对注解,并没有实现。

JPA的实现框架有:Hibernate EntityManager(RedHat)、TopLink Essentials(Oracle/GlassFish,EJB 3.0中的JPA参考实现),Apache OpenJPA(BEA)、EclipseLink(http://www.eclipse.org/eclipselink/)、JDO等ORM框架。

在Spring家族里,有Spring data jpa(https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#reference)提供ORM,Spring Data JPA的默认实现是Hibernate,当然也可以是其他的JPA Provider。如Spring Data JPA 1.10支持Querydsl 4、Hibernate 5、OpenJPA 2.4 和 EclipseLink 2.6.1。

1.2 Jpa 与mybatis

所以,JPA跟mybatis没关系。 然而,Spring data jpa的风格却特别优雅,我们可以用mybatis去实现这种风格。

如: Spring对JPA实现的核心的API:

  • Repository: 所有接口的父接口,而且是一个空接口,目的是为了统一所有Repository的类型,让组件扫描的时候能进行识。
  • CrudRepository:是Repository的子接口,提供CRUD的功能。
  • PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能。
  • JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,例如批量操作。

这些,我们完全可以用mybatis去实现。实现无sql的开发过程。

1.3 Spring data jpa实例

代码语言:javascript复制
@Entity
@Table(name = "user_role")
public class UserRole {
   @Id
   private int id;
   @Column(name = "user_name")
   private String userName;
   private String role;
}
public interface UserRoleDao extends CrudRepository<UserRole, Integer> {
   List<UserRole> findByRole(String role);
   @Query("select u from UserRole u where u.userName like ?1%")
   List<UserRole> findByAndSort(String userName, Sort sort);
}

public interface UserRoleDao extends PagingAndSortingRepository<UserRole, Integer> {
   Page<UserRole> findByUserName(String username, Pageable pageable);
}

二、Mybatis优雅编程

2.1 Mybatis原理

  • Configuration: MyBatis 或者 MyBatisPlus全局配置对象。
  • MappedStatement:一个 MappedStatement 对象对应 Mapper 配置文件中的一个。select/update/insert/delete 节点,主要描述的是一条 SQL 语句。
  • SqlMethod : 枚举对象 ,MyBatisPlus支持的 SQL 方法。
  • TableInfo:数据库表反射信息 ,可以获取到数据库表相关的信息。
  • SqlSource: SQL 语句处理对象。
  • MapperBuilderAssistant: 用于缓存、SQL 参数、查询方剂结果集处理等。通过 MapperBuilderAssistant 将每一个 mappedStatement 添加到onfiguration 中的 mappedstatements 中。

2.2 Mybatis代码生成工具概述

Mybatis的代码生成工具有很多: mybatis-generator、 easycode 等

这些工具是读取数据库表结构,然后按照模板生成entity、mapper、xml文件的。

如果不想下载插件,不想配置数据源,我们完全可以用js去自己生成code。 比如我自己做的一个:https://www.pomit.cn/java/project/sqlToMapper.html

生成代码,虽然提高了开发效率,但是不方便维护。 开发原则:约定大于配置,配置大于代码。所以有很多动态生成mybatis sqlsource的工具,如tk-mybatis、mybatis-plus。

2.3 Mybatis增强工具的原理

  • tk-mybatis:框架自定义的MapperFactoryBean重写了checkDaoConfig()方法,完成对所有sql语句的设置
  • mybatis-plus:继承MapperAnnotationBuilder,重写了MappedStatement。

Mybatis的MapperAnnotationBuilder是注解方式sql的处理器,其中parseStatement方法就是对Method上的注解进行解析,生成sqlsource并addMappedStatement,如果实现无sql的方式,只要有自己的parseStatement,并addMappedStatement即可。

那如何实现呢?

2.4 实现Spring data jpa风格的Mybatis

2.4.1 Mybatis的addMappedStatement
代码语言:javascript复制
public MappedStatement addMappedStatement(
     String id,
     SqlSource sqlSource,
     StatementType statementType,
     SqlCommandType sqlCommandType,
     Integer fetchSize,
     Integer timeout,
     String parameterMap,
     Class<?> parameterType,
     String resultMap,
     Class<?> resultType,
     ResultSetType resultSetType,
     boolean flushCache,
     boolean useCache,
     boolean resultOrdered,
     KeyGenerator keyGenerator,
     String keyProperty,
     String keyColumn,
     String databaseId,
     LanguageDriver lang,
     String resultSets) {

这就是一个MappedStatement所需要的东西,虽然很多,但是不用担心,真正需要的就几个:

  1. SqlSource,就是sql
  2. SqlCommandType定义CRUD类型
  3. resultType返回值类型
  4. keyGenerator主键生成策略

其他的不是很重要。最重要的是SqlSource该怎么写呢?

languageDriver.createSqlSource(configuration, sql, parameterTypeClass);

这里使用configuration、注解的sql和参数一起创建了SqlSource。所以,思路来了,我们可以使用Java Persistence API的注解和反射去拼接SQL!

2.4.2 获取Mapper

同时,我们也可以通过手段获取到Mapper。

代码语言:javascript复制
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;

for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
     org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
     MapperRegistry mapperRegistry = configuration.getMapperRegistry();
     List<Class<?>> mappers = new ArrayList<>(mapperRegistry.getMappers());
     // 可以记录下所有的mapper
}
// 此后可以扫描mapper的所有方法,可以按照方法是否存在注解去兼容mybatis常规写法和自动生成代码。

在Spring环境下,我们很容易获取到SqlSessionFactory,通过SqlSessionFactory,我们就可以获取到我们想要的东西: 这里,我们可以获取到Configuration和mappper。这两个很有用, 在MapperAnnotationBuilder中,会通过Configuration和mappper生成MapperBuilderAssistant,然后MapperBuilderAssistant调用addMappedStatement添加自定义MappedStatement。

2.4.3 定义实体

为了自动生成SQL,就需要我们根据实体去拼接SQL。那就解决两个问题:生成哪些方法的sql、实体从哪儿来。 当然有办法:

  1. 可以模仿Spring data jpa的CrudRepository和PagingAndSortingRepository,建立一个CrudMapper和PagingAndSortingMapper,加上泛型,泛型来作为实体,可以使用Java Persistence API的注解,如
代码语言:javascript复制
 import javax.persistence.Table;
 import javax.persistence.Column;
 import javax.persistence.Id;
 @Table(name = "f_auth_apply")
 public class UserAuthApply {
     @Id
     @Column(name = "user_name")
     private String userName;
     @Column(name = "role_name")
     private String roleName;
 }
  1. CrudMapper和PagingAndSortingMapper定义常用的方法,如save、update、delete等,并实现findBy 字段名进行查询、deleteBy 字段名规则。
  2. 让获取到的Mapper,继承CrudMapper和PagingAndSortingMapper,这样我们就可以通过解析继承的接口,获取到泛型类:
代码语言:javascript复制
Class<?> interfases[] = mapper.getInterfaces();
if (interfases == null || interfases.length < 1) {
        return NO_MAPPER;
}
for (Class<?> interfase : interfases) {
        if (ReflectUtil.checkTypeFit(interfase, JMapper.class)) {
                if (interfase.equals(SimpleShardingMapper.class)) {
                        return SHARDING_MAPPER;
                } else if (interfase.equals(PagingAndSortingMapper.class)) {
                        return PAGESORT_MAPPER;
                } else {
                        return CRUD_MAPPER;
                }
        }
}
  1. 同时,可以获取到Mapper的方法:
代码语言:javascript复制
Method[] methods = mapper.getMethods();
for (Method method : methods) {
        if (method.getAnnotations() == null || method.getAnnotations().length < 1) {
                //无注解,代表是需要被生成sql的方法,有注解无视即可,会自己走mybatis的逻辑。
        }
}
  1. 解析出实体,那自然就能拿到实体上的注解。解析出方法,自然可以按照方法的格式去生成sql了。
2.4.4 实例
代码语言:javascript复制
@Table(name="user_info")
public class UserInfo implements Serializable {
 private static final long serialVersionUID = 1L;

 @Id
 @Column(name="user_name")
 private String userName;

 @Column()
 //@ShardingKey(prefix="_", methodPrecis="getTable", methodRange = "getTables")
 private String mobile;

 @Column()
 private String name;

 @Column()
 private String passwd;

 @Column()
 private String valid;

}
代码语言:javascript复制
import org.apache.ibatis.annotations.Mapper;
import com.github.ffch.boot.domain.UserInfo;
import com.github.ffch.jpamapper.core.mapper.CrudMapper;

@Mapper
public interface UserInfoDao extends CrudMapper<UserInfo, String> {
   List<UserInfo> findByUserName(String userName);
   int deleteByUserName(String userName);

   @Select({
       "<script>",
           "SELECT ",
           " user_name as userName,",
           "role as role",
           "FROM user_info",
      "</script>"})
   List<UserInfo> selectAll();
}
代码语言:javascript复制
@Mapper
public interface UserInfoSortDao extends PagingAndSortingMapper<UserInfo, String>{
   Page<UserInfo> pageByPasswd(String passwd, Pageable pageable);
   List<UserInfo> sortByPasswd(String passwd, Sort sort);
}

2.5 可以做什么

这种方式仅仅可以固定的SQL么,非也。

  1. 可以实现findBy 字段名(And/OR)进行查询、deleteBy 字段名规则等,就是对检测到某正则匹配的方法名,where条件加上相应的字段名。
  2. 可以实现分页排序,对加上Pageable的方法,模拟@Result(property = “userList”, javaType=List.class, many =@Many(select=“selectUsersByGroupId”), column = “group_id”)})这种形式,对ResultType进行解析,隐式创建一个MappedStatement。
  3. 可以实现联表查询,可以对@JoinColumns和@JoinColumn的含义重写,同样类似分页去模拟一个@Many或者@One注解即可。
  4. 可以实现分表查询,例如指定某个字段为分表字段,制造SQL的时候使用<bind name=”patternTable“ value=”@com.cff.springbootwork.sharding.jdbc.domain.ChatInfo@findTable(liveId), 这种形式可以根据方法动态生成表名。

下面是三年前写的一个小工具: https://www.pomit.cn/jpa-mapper/#/

0 人点赞