QA 由浅入深持久层框架(十二)- MyBatis 通用 Mapper(Part C)

「这是我参与2022首次更文挑战的第37天,活动详情查看:2022首次更文挑战

六、自定义Mapper

业务Mapper接口PorscheMappr通过继承Mapper<T>接口从而获取了一系列的方法,这一系列的方法也不是Mapper<T>接口本身就有的,而是通过继承其他Mapper如BaseMapper<T>、ExampleMapper<T>等,而这些BaseMapper<T>又继承简介继承了SelectOneMapper<T>才获得selectOne方法,因此我们根据实际需要对Mapper<T>进行定制。

6.1 实现自定义Mapper

在common-mapper项目中新建一个common包,用来存放自定义的Mapper<T>,新建CustMapper<T>

public interface CustMapper<T> extends SelectOneMapper<T> {
}
复制代码

这里也可以选择继承多个如SelectAllMapper<T>等。

在Spring配置文件application.xml中配置自定义的CustMapper<T>

<bean id="mapperScannerConfigurer" class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--指定接口所在的包-->
    <property name="basePackage" value="com.citi.mapper"></property>
    <!--配置自定义的CustMapper<T>接口-->
    <property name="properties">
        <value>
            mappers=com.citi.common.CustMapper
        </value>
    </property>
</bean>
复制代码

修改TeacherMapper的继承关系,改为继承自定义的CustMapper<T>接口

public interface TeacherMapper extends CustMapper<Teacher> {
}
复制代码

增加TeacherMapper的测试类TeacherMapperTest,对继承来的selectOne方法进行测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class TeacherMapperTest {

    @Resource
    private TeacherMapper teacherMapper;

    @Test
    public void selectOne(){
        // 构造查询条件
        Teacher record = new Teacher();
        record.setGrade("三年二班");
        record.setName("stark");
        Teacher teacher = teacherMapper.selectOne(record);
        System.out.println("查询到的内容为:" + teacher);
    }
}
复制代码

执行测试 image.png 成功输出根据查询条件查到的数据

plus:自定义的Mapper和普通的XxxMapper接口不能放在同一个包下,会导致Spring容器创建自定义Mapper的Bean失败

七、通用Mapper扩展

扩展指的是增加通用Mapper没有的功能,通用Mapper提供了一些列基本的增删改查以及条件查询主键查询等方法,但是没有提供批量操作的方法,官网中给出了扩展通用Mapper的例子即扩展批量插入的功能。

7.1 扩展实现批量更新

自定义的Mapper扩展可以参考官方已有的代码。

image.png 仿照官方的UpdateByPrimaryKeyMapper<T>接口来写我们自己的BatchUpdateMapper<T>接口

@RegisterMapper
public interface BatchUpdateMapper<T> {

    @UpdateProvider(type = BatchUpdateProvider.class, method = "dynamicSQL")
    void batchUpdate(List<T> tList);
}
复制代码

在common包下新建一个BatchUpdateProvider image.png 仿照官方的BaseUpdateProvider实现自定义的BatchUpdateProvider,首先声明一个构造器

public class BatchUpdateProvider extends MapperTemplate {

    // 构造器
    public BatchUpdateProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
        super(mapperClass, mapperHelper);
    }
}
复制代码

批量更新的SQL语句在Mapper XML文件中写入如下格式,将多条UPDATE SQL语句通过“;”连接起来执行

<foreach item="record" collection="list" separator=";">
    UPDATE porsche
    <set>
    por_name = #{porsche.porName},
    por_price = #{porsche.porPrice},
    por_stock = #{porsche.porStock},
    </set>
    por_id = #{porsche.por_id}
</foreach>
复制代码

这就是我们要拼接的SQL语句,BatchUpdateProvider类中增加一个方法batchUpdate,该方法返回一个String即要执行的SQL语句

public String batchUpdate(MappedStatement ms){

    // 1.新建一条SQL语句
    StringBuilder sql = new StringBuilder();
    // 2.拼接 foreach标签开头
    sql.append("<foreach item="record" collection="list" separator=";" >");

    // 获取实体类对象
    Class<?> entityClass = getEntityClass(ms);
    
    String updateClause = SqlHelper.updateTable(entityClass,tableName(entityClass));

    // 3.拼接 "UPDATE 表名 "
    sql.append(updateClause);

    // 4.拼接set标签开头
    sql.append("<set>");

    // 获取字段名称的集合
    Set<EntityColumn> columns = EntityHelper.getColumns(entityClass);

    // 声明主键字段
    String idColumn = null;
    String idColumnHolder = null;

    // 遍历字段
    for (EntityColumn entityColumn : columns) {

        // 判断是否是主键,是主键要放在WHERE子句后面
        boolean isPrimaryKey = entityColumn.isId();
        System.out.println(isPrimaryKey);

        if (isPrimaryKey){
            idColumn = entityColumn.getColumn();
            System.out.println(idColumn);
            idColumnHolder = entityColumn.getColumnHolder("record");
        } else {

            // 返回类似如下字符串"(实体类.属性,jdbcType=NUMERIC,typeHandler=MyTypeHandler)"
            // 获取字段名和属性名
            String column = entityColumn.getColumn();
            String columnHolder = entityColumn.getColumnHolder("record");
            // 5.拼接por_name = #{porsche.porName},
            sql.append(column).append("=").append(columnHolder).append(",");
        }
    }

    // 6.拼接set标签结尾
    sql.append("</set>");

    // 7.拼接where子句
    sql.append("where ").append(idColumn).append("=").append(idColumnHolder);

    // 8.拼接 foreach标签结尾
    sql.append("</foreach>");
    // 9.返回SQL语句
    return sql.toString();
}
复制代码

代码中拼接批量更新SQL的步骤为:

  1. 新建一条SQL语句
  2. 拼接 foreach标签开头
  3. 拼接 "UPDATE porsche "
  4. 拼接 set标签开头
  5. 拼接 por_name = #{porsche.porName}
  6. 拼接 set标签结尾
  7. 拼接 where子句
  8. 拼接 foreach标签结尾
  9. 返回SQL语句

然后让自定义的CustMapper继承自定义的BatchUpdateMapper

public interface CustMapper<T> extends SelectOneMapper<T>,BatchUpdateMapper<T> {
}
复制代码

因为TeacherMapper接口继承了CustMapper,所有TeacherMapper接口就自动获得了batchUpdate方法,在TeacherMapperTest测试类中增加对batchUpdate的测试

@Test
public void batchUpdate(){
    List<Teacher> teacherList = new ArrayList<>();
    for (int i = 7; i < 10; i++) {
        Teacher teacher = new Teacher();
        teacher.setId(i);
        teacher.setGrade("五年" + i + "班");
        teacher.setName("Ultron " + i);
        teacher.setAddress("New York");
        teacher.setBirthDate(new Date());
        teacherList.add(teacher);
    }
    teacherMapper.batchUpdate(teacherList);
}
复制代码

执行测试前需要注意因为更新语句是将多条SQL语句通过“;”连接起来一次执行,所有需要在db.properties中jdbc_url后面要加上“&allowMultiQueries=true“。

执行测试 image.png

这里出现错误,根据输出的SQL语句判断应该是isId()方法没有判断出id是主键,查看Teacher实体类,发现id属性上没有增加@Id注解,也就是说通用Mapper并不知道id属性对应的字段是主键,也就没有做出正确的判断,导致输出控制台的错误语句。 在id属性上增加@Id注解以及@GeneratedValue注解

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
复制代码

再次执行测试

image.png 数据库被成功修改。

自定义的批量更新扩展生效。

Guess you like

Origin juejin.im/post/7068674209043447821