Skip to content

StupidMybatis是一个mybatis扩展框架,提供了可扩展的mybatis注解增强以及通过注解实现的核心能力的增强,用于简化mybatis映射接口的使用以及基于mybatis的DAO开发

Notifications You must be signed in to change notification settings

gaohanghbut/stupidmybatis

Repository files navigation

StupidMybatis

StupidMybatis是一个mybatis扩展框架,提供了可扩展的mybatis注解增强以及通过注解实现的核心能力的增强,用于简化mybatis映射接口的使用以及基于mybatis的DAO开发。

基础架构

StupidMybatis

StupidMybatis为增强mybatis映射接口,并使得映射接口的注解具备较强的扩展性开发,从下到上分为4层

  • 基础框架层:StupidMybatis基于mybatis, spring-mybatis以及mybatis-spring-boot开发
  • 核心层:StupidMybatis的核心能力
    • 提供可扩展的注解机制,分为配置处理注解与运行处理注解两部分,分别用于增强mybatis的配置和增强mybatis的数据访问的执行能力
    • 基于可扩展的注解机制提供一定的核心功能增强
    • 增强插件:用于增强mybatis核心能力的mybatis插件(这部分后面可提升到上一层)
  • orm层:封装基于StupidMybatis核心层的基础DAO操作,减少DAO层的开发成本,提高DAO层的开发速度
  • 初始化层,包含三个部分:
    • 对spring的支持:用于支持传统的spring-mybatis的整合开发
    • 对spring-boot的支持:提供快速自动配置能力

框架特性

  • 默认ResultMap,SQL不指定resultMap则使用默认resultMap
  • 无SQL的注解支持,不需要写SQL就能完成单表的CURD操作
  • 支持用于SQL替换的宏,支持自定义宏
  • 常用DAO方法(CURD)的封装,不需要重新开发
  • 可重用的@Results,相同的映射不需要多次定义@Results,可使用@TypeResultMap定义一次
  • 使用default method作为ResultMap
  • 支持映射接口中默认方法的调用
  • 排序分页查询的增强(select * from table_name where id > #{lastPageMaxId} order by id limit #{pageSize})
  • 注解可扩展,可通过注解的扩展定制MappedStatement,给框架新增特性
  • 可通过注解扩展,对查询结果做后置处理
  • 批量更新插件
  • in查询插件
  • 自动分页查询所有数据
  • 完全兼容原生mybatis
  • 支持mybatis-spring和mybatis-spring-boot

使用配置

使用spring

使用StupidMybatis,先将spring中的SqlSessionFactoryBean替换成StupidSqlSessionFactoryBean,使用方式与SqlSessionFactory相同,例如:

  <bean id = "sqlSession" class="cn.yxffcode.stupidmybatis.spring.StupidSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
  </bean>

使用spring-boot

如果使用spring-boot,需要额外添加一个注解@EnableStupidMybatis,例如:

@SpringBootApplication
@EnableStupidMybatis
@MapperScan(basePackages = "xxx")//扫描标记了@Mapper的接口
public class MyApplication{
  public static void main(String[] args){
    SpringApplcation.run(MyApplication.class);
  }
}

不使用Spring

如果不使用spring,则需要将SqlSessionFactoryBuilder替换成StupidSqlSessionFactoryBuilder,例如:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new StupidSqlSessionFactoryBuilder().build(inputStream);

ORM

默认ResultMap

StupidMybatis提供了简单的ORM功能,ORM功能提供了默认使用的ResultMap,所有没有标记@Result且没标记@ResultMap 的方法,会使用ORM中提供的ResultMap,使用方式:

@TypeResultMap(id = "userResultMap", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name_t")
})
//配置ORM信息
@ORM(tableName = "user", resultMap = "userResultMap", primaryKey = @PrimaryKey(keyColumns = "id", autoGenerate = false))
public interface UserDao {

  @Select("select id, name_t from user")
  List<User> selectAll();//没有指定@Result或者@ResultMap,会使用@ORM中指定的resultMap

}

无SQL注解

提供了无SQL的@ORMSelect, @ORMInsert, @ORMUpdate和@ORMDelete注解。无SQL注解适用于单表的简单操作,复杂操作如left join需要使用原生mybatis

@ORMSelect

@ORMSelect用于做数据库查询,@ORMSelect可指定查询出哪些属性(DO中的属性,非表中的字段),如果没指定, 则表示查询出所有@ORM中指定的resultMap中的所有字段,使用方式如下:

@TypeResultMap(id = "userResultMap", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name_t")
})
@ORM(tableName = "user", resultMap = "userResultMap", primaryKey = @PrimaryKey(keyColumns = "id", autoGenerate = false))
public interface UserDao {

  /**
   * 查询所有,不提供查询参数,没指定查询字段,则查出ORM配置中指定的所有字段
   */
  @ORMSelect
  List<User> selectAll();

  /**
   * 提供查询参数,按name属性查询,没指定查询字段,则查出ORM配置中指定的所有字段
   */
  @ORMSelect
  User selectByName(@Param("name") String name);

  /**
   * 提供查询参数,按id和name属性查询,没指定查询字段,则查出ORM配置中指定的所有字段
   */
  @ORMSelect
  List<User> selectByParams(@Param("id") int id, @Param("name") String name);

  /**
   * 提供查询参数,按id属性查询,指定查询name属性
   */
  @ORMSelect(properties = "name")
  List<String> selectNames(@Param("id") int id);

}

@ORMInsert, @ORMUpdate, @ORMDelete的使用方式与ORMSelect类似:

@TypeResultMap(id = "userResultMap", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name_t")
})
@ORM(tableName = "user", resultMap = "userResultMap", primaryKey = @PrimaryKey(keyColumns = "id", autoGenerate = false))
public interface UserDao {

  @ORMSelect
  List<User> selectAll();

  @ORMInsert
  int insertUser(User user);

  @ORMUpdate
  int updateUser(User user);

  @ORMUpdate
  int updateUserByParams(@Param("id") int id, @Param("name") String name);

  @ORMSelect
  User selectByName(@Param("name") String name);

  @ORMSelect
  List<User> selectByParams(@Param("id") int id, @Param("name") String name);

  @ORMSelect
  List<User> selectByUserParams(User user);

  @ORMSelect(properties = "name")
  List<String> selectNames(@Param("id") int id);

  @ORMDelete
  int deleteUser(@Param("id") int id);

  @ORMDelete(conditions = "name")
  int deleteUserByName(@Param("name") String name);

}

其它复杂sql,需要使用原生的mybatis注解@Select/@SelectProvider, @Update/@UpdateProvider等

无SQL注解的order by, group by, limit

@TypeResultMap(id = "userResultMap", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name_t")
})
@ORM(tableName = "user", resultMap = "userResultMap", primaryKey = @PrimaryKey(keyColumns = "id", autoGenerate = false))
public interface UserDao extends BaseDataAccess<User, Integer> {

  @ORMSelect
  @OrderBy(@OrderBy.Order(value = "id", sort = OrderBy.Sort.DESC))
  @GroupBy({"name", "id"})
  @Limitation(offsetParam = "offset", limitParam = "limit")
  List<User> select(@Param("offset") int offset, @Param("limit") int limit);
}

宏用于对比sql做文本替换,想要使用宏,DAO映射接口上必须通过@ORM注解标记

内置宏

StupidMybatis提供了3个内置宏(@properties, @columns, @primaryKey),可以sql中使用,例如:

@TypeResultMap(id = "userResultMap", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name_t")
})
@ORM(tableName = "user", resultMap = "userResultMap", primaryKey = @PrimaryKey(keyColumns = "id", autoGenerate = false))
public interface UserDao {

  @Select("select @columns from user order by id limit #{offset}, #{limit}")
  List<User> selectPage(@Param("offset") int offset, @Param("limit") int limit);

}

执行的sql将会是:select id, name_t from user order by id limit ?, ?

自定义宏

可通过@MacroDeclare和@Macro注解自定义宏,@Macro的定义为:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Macro {
  /**
   * @return 宏名
   */
  String name();

  /**
   * @return 宏的内容,如果没有指定{@link #contentProvider()}则使用value的值
   */
  String value() default "";

  /**
   * @return 处理宏,替换为sql中的内容的SqlContentProvider,默认为取value的值
   */
  Class<? extends SqlContentProvider> contentProvider() default ValueSqlContentProvider.class;
}

自定义宏的使用方式:

@TypeResultMap(id = "userResultMap", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name_t")
})
@ORM(tableName = "user", resultMap = "userResultMap", primaryKey = @PrimaryKey(keyColumns = "id", autoGenerate = false))
@MacroDeclare({
    @Macro(name = "statementId", contentProvider = StatementIdSqlContentProvider.class),
    @Macro(name = "notDeleted", value = "status != 0")
})
public interface UserDao extends BaseDataAccess<User, Integer> {

  @Select("select /* @statementId */ @columns from user where @notDeleted order by id limit #{offset}, #{limit}")
  List<User> selectPage(@Param("offset") int offset, @Param("limit") int limit);

}

StatementIdSqlContentProvider的实现很简单,用于返回statementId:

public class StatementIdSqlContentProvider implements SqlContentProvider {
  @Override
  public String getContent(Macro macro, TableMetaCache.ORMConfig ormConfig, MappedStatement mappedStatement) {
    return mappedStatement.getId();
  }
}

此处定义了两个宏,分别说明了@KeyWord中的contentProvider和value的用法,最终执行的sql为:

select /* cn.yxffcode.stupidmybatis.data.UserDao.selectPage */ id,name_t from user where status != 0 order by id limit ?, ?

DAO的通用方法

DAO中会有一些共有的方法(insert, batchInsert, update, batchUpdate,selectById等,详情见BaseDataAccess接口), BaseDataAccess接口支持如下方法:

public interface BaseDataAccess<DO, ID> {

  /**
   * 插入数据
   */
  int insert(DO object);

  /**
   * 批量插入
   */
  int batchInsert(List<DO> objects);

  /**
   * 更新数据
   */
  int update(DO object);

  /**
   * 批量更新数据
   */
  int batchUpdate(List<DO> objects);

  /**
   * 通过id查询
   */
  DO selectById(ID id);

  /**
   * 条件查询
   *
   * @param condition 查询条件
   * @return 查询结果
   */
  List<DO> select(DO condition);

  /**
   * 范围查询,带上=参数
   *
   * @param condition 需要相等的条件参数
   * @param range     范围参数
   */
  List<DO> selectFixedRange(DO condition, Range range);

  /**
   * 范围查询
   */
  List<DO> selectRange(Range range);

}

这里不提供delete方法,数据一般只逻辑删除。

StupidMybatis通过@ORM的配置,提供了BaseDataAccess作为父接口,使得映射接口中不需要再重复的定义一些基础的方法,使用方式:

@TypeResultMap(id = "userResultMap", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name_t")
})
//这里一定要配置@ORM,指定表名,默认的resultMap和主键
@ORM(tableName = "user", resultMap = "userResultMap", primaryKey = @PrimaryKey(keyColumns = "id", autoGenerate = false))
//需要继承BaseDataAccess<DO, ID>接口
public interface UserDao extends BaseDataAccess<User, Integer> {

  @Select("select id, name_t from user")
  List<User> selectAll();

}

UserDao中可使用BaseDataAccess中的方法,不需要做其它编码,例如(spring的方式,非spring-boot):

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class ORMTest {

  @Resource
  private SqlSessionFactory sqlSessionFactory;

  @Test
  public void test() throws IOException {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);

    User user = new User();
    user.setId(0);
    user.setName("test");
    userDao.batchInsert(Collections.singletonList(user));
    user.setId(1);
    userDao.insert(user);

    System.out.println("userDao.selectAll() = " + userDao.selectAll());//UserDao中的方法
    System.out.println("userDao.selectById() = " + userDao.selectById(0));//BaseDataAccess中的通用方法

    user.setName("joh");
    System.out.println("userDao.update() = " + userDao.update(user));
    System.out.println("userDao.selectAll() = " + userDao.selectAll());
    user.setName("john");
    System.out.println("userDao.batchUpdate() = " + userDao.batchUpdate(Collections.singletonList(user)));
    System.out.println("userDao.selectAll() = " + userDao.selectAll());

  }
}

范围查询的使用方式

ConditionRange conditionRange = new ConditionRange("id", 0, 100);
System.out.println("userDao.rangeSelect() = " + userDao.selectRange(conditionRange));
Range range = new LogicRange(conditionRange, LogicRange.Logic.AND, new ConditionRange("name", "a", "z"));
System.out.println("userDao.rangeSelect() = " + userDao.selectRange(range));

LogicRange可对任意的Range做AND或者OR的组合

可复用的Result注解

mybatis提供的@esults注解只能标记在声明注解的方法上,如果有多个查询方法需要使用到相同的@Results注解, 只能再次配置一遍@Result或者使用xml,stupidmybatis提供了@TypeResultMap,可使得通过注解配置的Result 可复用

使用方式

原始的通过@Result配置的代码如下:

public interface UserDao {

  @Select({
      "select id, name from user"
  })
  //p定义resultmap
  @Results({
      @Result(property = "id", column = "id"),
      @Result(property = "name", column = "name")
  })
  List<User> selectAll();

  @Select("select id, name from user where id = #{id}")
  //再次定义相同的resultmap
  @Results({
        @Result(property = "id", column = "id"),
        @Result(property = "name", column = "name")
    })
  User selectById(@Param("id") int id);
}

可以看到需要重复定义@Results,当使用@TypeResultMap后的代码:

public interface UserDao {

  @Select({
      "select id, name from user"
  })
  @ResultMap("userMapper")
  List<User> selectAll();

  @Select("select id, name from user where id = #{id}")
  @ResultMap("userMapper")
  User selectById(@Param("id") int id);

  /**
   * 定义ResultMap, 可指定id和resultType作为resultMapId和resultMap的返回类型, 
   * 默认的resultMapId为方法名,默认的返回类型是方法的返回类型或者方法的返回类型中的元素
   */
  @TypeResultMap({
      @Result(property = "id", column = "idFactory"),
      @Result(property = "name", column = "name")
  })
  User userMapper();
}

TypeResultMap还可声明在Mapper接口的类型上,但必须要指定id和resultType,无法使用这两个属性的默认值:

/**
 * 定义ResultMap, 必须指定id和resultType作为resultMapId和resultMap的返回类型
 */
@TypeResultMap(id = "userMapper", resultType = User.class, value = {
  @Result(property = "id", column = "idFactory"),
  @Result(property = "name", column = "name")
})
public interface UserDao {

  @Select({
      "select id, name from user"
  })
  @ResultMap("userMapper")
  List<User> selectAll();

  @Select("select id, name from user where id = #{id}")
  @ResultMap("userMapper")
  User selectById(@Param("id") int id);
}

指定默认方法作为ResultMap

@Results/@TypeResultMap只是简单的映射,没有任务的转换逻辑,假如转换成POJO的时候,需要有转换逻辑(例如多个字段拼接) 可以指定一个默认方法作为ResultMap,使用方式如下:

public interface UserDao {

  @Select("select id, name from user where id = #{id}")
  @MapperMethod("mapToUser")//指定mapToUser方法作为ResultMap
  User selectById(@Param("id") int id);

  /**
   * 此方法作为ResultMap,这种用法需要Java8以上
   */
  default User mapToUser(Map<String, ?> result) {
    if (result == null) {
      return null;
    }
    User user = new User();
    //mybatis返回的map中,字段都是大写
    user.setId((Integer) result.get("ID"));
    user.setName((String) result.get("NAME"));
    return user;
  }
}

映射方法的参数需要是Map

指定默认方法作为ResultMap这种使用方式,还可以结合其它ResultMap,例如:

public interface UserDao {

  @Select("select id, name from user where id = #{id}")
  @ResultMap("mapMapper")//指定一个名为mapMapper的返回Map的ResultMap
  @MapperMethod("mapToUser")//转换mapMapper返回的Map
  User selectById(@Param("id") int id);
  
  /**
   *  mapMapper is a result map
   */
  @TypeResultMap({
      @Result(property = "id", column = "id"),
      @Result(property = "name", column = "name")
  })
  Map mapMapper();

  /**
   * @param result
   * @return
   */
  default User mapToUser(Map<String, ?> result) {
    if (result == null) {
      return null;
    }
    User user = new User();
    user.setId((Integer) result.get("id"));
    user.setName((String) result.get("name"));
    return user;
  }

}

上面的例子中,先使用mapMapper将结果转换成Map,再通过mapToUser方法将Map转换成User。

同时使用ResultMap和映射方法的时候,映射方法的参数需要和ResultMap的返回类型相同, 假如指定的ResultMap返回的是一个POJO对象,比如User,则映射方法的参数需要是User,例如:

public interface UserDao {

  @Select("select id, name from user where id = #{id}")
  @ResultMap("userMapper")
  @MapperMethod("userTransform")
  User selectById(@Param("id") int id);

  @TypeResultMap({
      @Result(property = "id", column = "id"),
      @Result(property = "name", column = "name")
  })
  User userMapper();

  default User userTransform(User user) {
    if (user == null) {
      return null;
    }
    user.setName("hello " + user.getName());//修改name的值
    return user;
  }

}

调用映射接口的默认方法

原生mybatis不支持java8的默认方法调用,会有找不到statement的异常,stupidmybatis支持默认方法的调用,例如如下DAO中,mapToUser方法可单独调用

public interface UserDao {

  @Select("select id, name from user where id = #{id}")
  @MapperMethod("mapToUser")
  User selectById(@Param("id") int id);

  default User mapToUser(Map<String, ?> result) {
    if (result == null) {
      return null;
    }
    User user = new User();
    user.setId((Integer) result.get("id"));
    user.setName((String) result.get("name"));
    return user;
  }

}

调用默认方法:

Map<String, Object> map = Maps.newHashMap();
map.put("id", 0);
map.put("name", "hello");
User user = userDao.mapToUser(map);

排序分页查询的增强

分页查询通常需要使用offset和limit两个参数,当表中的数据量非常大时,offset会有比较差的性能, 一种优化的分页方式是维护一个id,每次查询时作为参数传入,例如:

@TypeResultMap(id = "userResultMap", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name")
})
@Mapper
public interface UserDao {

  @Select("select id, name from user where id > #{lastMaxId} order by id asc limit #{pageSize}")
  @ResultMap("userResultMap")
  List<User> select(@Param("lastMaxId") int lastMaxId, @Param("pageSize") int pageSize);
}

在select方法查询到结果后,需要在service中取出最大的id,作为查询下一页时的入参,例如:

public ServiceResult doService(int lastMaxId, int pageSize) {
  List<User> users = userDao.select(lastMaxId, pageSize);
  
  if (CollectionUtils.isEmpty(users)) {
    return emptyResult();//表示空结果 
  }
  User user = users.get(users.size() - 1);//获取最后一个id
  int maxId = user.getId();
  return new ServiceResult(maxId, users);
}

整个过程比较繁琐,有不少用于处理非业务相关的代码,StupidMybatis提供了OrderPageList和OrderPagination来解决这种分页查询,例如:

@Mapper
public interface UserDao {

  @Select("select id, name from user where id > #{lastMaxId} order by id asc limit #{pageSize}")
  @Results({
      @Result(property = "id", column = "id"),
      @Result(property = "name", column = "name")
  })//这里也可以使用@ResultMap
  @OrderPagination("id")//指定哪个属性表示的是分页字段的值,如果主键有多个字段,暂时只支持多个字段通过一个类组合成属性的情况
  OrderPageList<User, Integer> select(@Param("lastMaxId") int lastMaxId, @Param("pageSize") int pageSize);
}

OrderPageList中会包含查询结果,通过OrderPageList.getLastId()可获取最后一个元素的id

注解扩展

对返回结果进行特定的加工

除了上面@MapperMethod能调用默认方法处理返回结果外,还可通过自定义注解的方式处理返回结果, 自定义注解需要通过@MapperResultHandler元注解提供MapperResultPostHandler的实现类, 例如实现一个@PostProcessResult用于打印日志:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@MapperResultHandler(PostProcessResult.PostProcesser.class)//这里指定MapperResultPostHandler
public @interface PostProcessResult {

  /**
   * 处理返回结果的类
   */
  final class PostProcesser implements MapperResultPostHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(PostProcessResult.class);

    @Override
    public Object handle(Class<?> type, Method method, Object proxy, Object result) throws Throwable {
      LOGGER.info("{}.{} result is {}", type.getName(), method.getName(), result);
      return result;
    }
  }
}

可在DAO上使用@PostProcessResult

@TypeResultMap(id = "userMapper", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name")
})
public interface UserDao {

  @Select("select id, name from user where id = #{id}")
  @ResultMap("userMapper")
  @PostProcessResult//此处可使用自定义的结果处理注解
  User selectById(@Param("id") int id);

}

通过注解对配置的扩展

可自定义注解用于对mybatis进行配置,注解中需要通过@MapperConfHandler元注解指定 MapperConfigHandler接口的实现,例如实现一个自动在sql后面拼上limit的注解@Limit:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@MapperConfHandler(value = Limit.Config.class, order = MapperConfHandler.Order.AFTER_CONFIG_PARSE)
public @interface Limit {

  /**
   * @return limit 的大小
   */
  int value();

  /**
   * 处理返回结果的类
   */
  final class Config implements MapperConfigHandler<Limit> {
    @Override
    public void handleAnnotation(Limit limit, Class<?> type, Method method, MapperBuilderAssistant assistant) throws Throwable {
      //通过assistant注册配置,不清楚可看看mybatis源码
      String statementId = type.getName() + '.' + method.getName();
      MappedStatement mappedStatement = assistant.getConfiguration().getMappedStatement(statementId);
      
      Reflections.setField(mappedStatement, "sqlSource", new SqlSource() {
        private final SqlSource delegate = mappedStatement.getSqlSource();
        private final Configuration configuration = assistant.getConfiguration();

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
          BoundSql boundSql = delegate.getBoundSql(parameterObject);
          return new BoundSql(configuration, boundSql.getSql() + " limit " + limit.value(), boundSql.getParameterMappings(), boundSql.getParameterObject());
        }
      });
    }

  }
}

在DAO上使用@Limit

@TypeResultMap(id = "userMapper", resultType = User.class, value = {
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name")
})
public interface UserDao {

  @Select("select id, name from user where id >= #{minId}")
  @ResultMap("userMapper")
  @Limit(100) //limit 100
  List<User> selectUsers(@Param("minId") int id);

}

Mybatis批量插件

mybatis已有的批量更新比较麻烦,要么写动态sql,要么利用BatchExecutor的SqlSession. 在工程中,更加希望DAO中的方法需要批量的时候用批量,不需要批量的时候不用批量. 有两种方式 实现,一种是实现自定义的Executor,它持有batch与非batch的两个Executor,在执行sql时自 由切换,第二种实现方式则是通过mybatis 插件实现,当需要使用批量时,不使用sqlsession中的 executor,而是使用新的executor. 第一种方式相对稍复杂一点,第二种方式需要将此插件配置 成第一个Executor插件.这里选择使用第二种方式。

此插件用于优化mybatis批量插入/更新/删除的插件. 此插件基于BatchExecutor实现批量更新, 只需要将需要更新的sql id(不包含命名空间)以batch开头,参数需要是Iterable或者数组即可。

使用方式

1.配置mybatis插件

<plugin interceptor="cn.yxffcode.stupidmybatis.core.BatchExecutorInterceptor"></plugin>

如果使用了为spring-boot提供的@EnableStupidMybatis注解,则不需要再配置此插件,@EnableStupidMybatis注解会自动配置 2.DAO如果需要使用batch则,参数需要是Iterable或者数组,sql的statement id(不包含命名空间) 要以batch开头,如果是映射接口,则方法名以batch开头:

public interface UserDao {

  @Insert({
          "insert into user (id, name) values (#{id}, #{name})"
  }) 
  int batchInsert(List<User> users);
}

使用建议

此插件的实现原理是拦截Executor的update方法,然后将目标方法的调用改为创建新的BatchExecutor, 然后执行批量的更新, 但新的BatchExecutor对象没有经过InterceptorChain的包装,所以在此插件之前 的Executor拦截器不会被执行,所以最好是将此插件配置在第一个。

Mybatis in查询参数插件

在使用mybatis做in查询时,需要写动态sql,如果使用xml则要写forEach标签,如果使用注解,则需要写SqlProvider。 此插件为mysql支持参数为Iterable或者数组的情况,自动将in(#{list})转换成in(?, ?, ?)的形式, 不需要再写forEach或者SqlProvider。

使用方式

<plugin interceptor="cn.yxffcode.stupidmybatis.core.ListParameterResolver"></plugin>

如果使用了为spring-boot提供的@EnableStupidMybatis注解,则不需要再配置此插件,@EnableStupidMybatis注解会自动配置,

DAO上的in查询示例:

public interface UserDao {

  @Insert({
          "insert id, name from user where id in (#{userIds})"
  })
  @Results({
    //省略
  }) 
  List<User> selectByIds(@Param("userIds") List<Integer> userIds);
}

自动分页查询所有数据

mybatis dao自动分页查询。如果在系统启动时需要将各种不同的基础数据全部加载到内存中,可以使用分页查询的方式, 但是会有大量处理分页查询的代码,此插件用于自动执行分页

使用方式

  1. 配置mybatis插件
<plugin interceptor="cn.yxffcode.stupidmybatis.core.PageQueryAllInterceptor"></plugin>

如果使用了为spring-boot提供的@EnableStupidMybatis注解,则不需要再配置此插件,@EnableStupidMybatis注解会自动配置 2. 对于DAO,需要使用此插件的查询方法上加上注解@Paged:

@Select({
        "select id, name from user"
})
@Results({
  //省略
})
@Paged
List<User> selectAll();

@Paged注解可以标记在类或者接口的方法上,value属性表示一页的大小,默认为100

  1. 创建DAO代理,用于处理分页上下文
UserDao userDao = PagedQueryDaoProxy.wrapNotNull(sqlSession.getMapper(UserDao.class));

使用Spring

每次使用PagedQueryDaoProxy创建代理比较繁琐,如果是Spring工程,则可以通过Spring自动创建代理。

在Dao上使用任何注解标记,例如@Repository,然后在spring中配置DaoPageQueryAllBeanPostProcessor

<bean class="cn.yxffcode.mybatispagequeryall.DaoPageQueryAllBeanPostProcessor">
    <constructor-arg value="org.springframework.stereotype.Repository"/>
</bean>

如果使用了为spring-boot提供的@EnableStupidMybatis注解,则不需要再配置此插件,@EnableStupidMybatis注解会自动配置

About

StupidMybatis是一个mybatis扩展框架,提供了可扩展的mybatis注解增强以及通过注解实现的核心能力的增强,用于简化mybatis映射接口的使用以及基于mybatis的DAO开发

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages