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

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

八、通用Mapper的二级缓存

8.1 通用Mapper缓存测试

在PorscheMapperTest测试类中增加一个testCache测试方法

@Test
public void testCache(){
    // 第一次执行selectAll
    List<Porsche> porscheList = porscheMapper.selectAll();
    for (int i = 0; i < 3; i++) {
        System.out.println("第一次查询到的前三条:" + porscheList.get(i).getPorName());
    }

    // 第二次执行selectAll
    List<Porsche> porsches = porscheMapper.selectAll();
    for (int i = 0; i < 3; i++) {
        System.out.println("第二次查询到的前三条:" + porsches.get(i).getPorName());
    }
}
复制代码

执行测试 image.png 调用了两次selectAll方法,控制台输出了两条SQL语句,说明并没有直接从缓存中提取数据

8.2 通用Mapper二级缓存配置

通用Mapper二级缓存的开启与原生MyBatis二级缓存的开启有相同的地方也有不同的地方

相同点:

  • 都需要在MyBatis全局配置文件中开启二级缓存
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
复制代码
  • 都需要Entity实体类实现Serializable接口

不同点

  • 原生MyBatis还需要在Mapper XML中的mapper标签下添加cache标签
  • 通用Mapper由于没有Mapper XML,所以他的做法是在Mapper接口上增加@CacheNamespace,以实现同样的效果

原生MyBatis的一级缓存和二级缓存可以参考QA 由浅入深持久层框架(七)- MyBatis Cache

配置完成之后再次对PorscheMapperTest中的testCache方法执行测试

image.png

九、通用Mapper的TypeHandler

9.1 简单类型和复杂类型

基本数据类型与引用数据类型

  • 基本数据类型:byte、short、int、long、double、float、char、boolean
  • 引用数据类型:接口、类、数组、枚举

简单类型与复杂类型

  • 简单类型:只有一个值的类型
  • 复杂类型:多个简单类型组合起来

9.2 通用Mapper处理复杂类型数据

9.2.1 搭建common-mapper-typehandler项目

新建一个项目common-mapper-typehandler,项目依赖以及配置文件可以参考common-mapper项目。

创建table_user表,并插入一条数据

DROP TABLE IF EXISTS `table_user`;
CREATE TABLE `table_user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(100) DEFAULT NULL,
  `address` varchar(100) DEFAULT NULL,
  `season` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of table_user
-- ----------------------------
BEGIN;
INSERT INTO `table_user` VALUES (1, 'stark', 'State of New York,New York City,Marbury Street', 'SPRING');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
复制代码

entity中创建User、Address实体类以及SeasonEnum枚举类

@Data
@Table(name = "table_user")
public class User {
    @Id
    private Integer userId;
    private String userName;
    private Address address;
    private SeasonEnum season;
}
复制代码
@Data
public class Address {

    private String province;
    private String city;
    private String street;
}
复制代码
public enum SeasonEnum {
    SPRING("spring"),SUMMER("summer"),AUTUMN("autumn"),WINTER("winter");

    private String seasonName;
    private SeasonEnum(String seasonName){
        this.seasonName = seasonName;
    }

    public String getSeasonName(){
        return this.seasonName;
    }

    public String toString(){
        return this.seasonName;
    }
}
复制代码

User实体类中Integer和String可以称作简单类型,Address和SeasonEnum属性可以称为复杂类型。

新建mapper包,增加UserMapper接口并继承通用Mapper的Mapper接口

public interface UserMapper extends Mapper<User> {
}
复制代码

新建service及impl包,增加UserService接口以及UserServiceImpl实现类,新增getUserById方法

public interface UserService {

    User getUserById(Integer id);
}
复制代码
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserById(Integer id) {

        return userMapper.selectByPrimaryKey(id);
    }
}
复制代码

在test包中新增UserServiceTest,对UserService中的getUserById方法进行测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application.xml")
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void getUserById() {
        User user = userService.getUserById(1);
        System.out.println("查询到的内容为:" + user);
    }
}
复制代码

执行测试

image.png 输出的User对象address属性和season属性都是空的

这是因为通用Mapper默认把复杂类型Address和SeasonEnum忽略掉了,默认只处理简单类型

再来试试insert方法,在UserServiceTest中增加saveUser方法的测试

@Test
public void saveUser(){
    User use = new User();

    use.setUserName("banner");
    Address address = new Address();
    address.setProvince("State of New York");
    address.setCity("New York City");
    address.setStreet("Unkown");
    use.setAddress(address);
    use.setSeason(SeasonEnum.SPRING);

    userService.saveUser(use);

}
复制代码

执行测试 image.png INSERT语句中address字段和season字段的值是null

通用Mapper默认情况下会忽略复杂类型,对复杂类型不进行“从类到表”的映射

9.3 自定义类型处理器TypeHandler

以上问题的解决方式有两种。第一种是新建一张address表,建立user表到address表的关联关系,在MyBatis Mapper XML中使用resultMap和collection标签重新定义映射关系

第二种方式是不创建新的表,就将Address属性的内容全部存到表的address字段中,这就需要使用到自定义的类型处理器

自定义类型处理器要注意字段存储的内容为字符串,所以自定义的类型处理器的主要功能是建立一个规则,将address属性转化为字符串存储在数据库中,并按照一定的格式存储,这个规则还包括查询时,将字符串转化为实体类类型。

image.png

查看类型转换的顶级接口TypeHandler以及BaseTypeHandler

image.png

image.png

  • setNonNullParameter:将要做类型处理的parameter对象转换为字符串存在ps对象的i位置
  • getNullableResult:从结果集中获取查询结果转换为原始对象

可以通过继承BaseTypeHandler实现自定义的类型处理器

9.3.1 实现自定义类型处理器AddressTypeHandler

新建一个类AddressTypeHandler继承BaseTypeHandler

public class AddressTypeHandler extends BaseTypeHandler<Address> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Address parameter, JdbcType jdbcType) throws SQLException {
        // 1.对传进来的address对象进行校验
        if (parameter == null){
            return;
        }

        // 2.从address对象中取出数据
        String province = parameter.getProvince();
        String city = parameter.getCity();
        String street = parameter.getStreet();

        // 3.拼接成一个字符串,用","隔开
        StringBuilder builder = new StringBuilder();
        builder.append(province).append(",").append(city).append(",").append(street);

        // 4.设置参数
        String parameterValue = builder.toString();
        ps.setString(i,parameterValue);

    }

    @Override
    public Address getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 根据字段名从rs对象中获取字段值
        String columnValue = rs.getString(columnName);
        // 校验 columnValue是否有效
        if (columnValue == null || columnValue.length() == 0|| !columnValue.contains(",")){
            return null;
        }
        // 拆分columnValue
        String[] split = columnValue.split(",");

        // 从拆分结果中给对象赋值
        Address address = new Address();
        address.setProvince(split[0]);
        address.setCity(split[1]);
        address.setStreet(split[2]);
        return address;
    }

    @Override
    public Address getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        // 根据字段索引从rs对象中获取字段值
        String columnValue = rs.getString(columnIndex);
        // 校验 columnValue是否有效
        if (columnValue == null || columnValue.length() == 0|| !columnValue.contains(",")){
            return null;
        }
        // 拆分columnValue
        String[] split = columnValue.split(",");

        // 从拆分结果中给对象赋值
        Address address = new Address();
        address.setProvince(split[0]);
        address.setCity(split[1]);
        address.setStreet(split[2]);
        return address;
    }

    @Override
    public Address getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        // 根据字段索引从cs对象中获取字段值
        String columnValue = cs.getString(columnIndex);
        // 校验 columnValue是否有效
        if (columnValue == null || columnValue.length() == 0|| !columnValue.contains(",")){
            return null;
        }
        // 拆分columnValue
        String[] split = columnValue.split(",");

        // 从拆分结果中给对象赋值
        Address address = new Address();
        address.setProvince(split[0]);
        address.setCity(split[1]);
        address.setStreet(split[2]);
        return address;
    }
}
复制代码

注册AddressTypeHandler的方式有两种

  • 字段/属性级别注册:在要使用自定义类型转换器的属性上使用@ColumnType注解
  • 全局注册:在MyBatis全局配置文件中使用typeHandlers标签注册,并在要转换的属性上增加@Colum注解。

首先使用@ColumnsType注解注册

@ColumnType(typeHandler = AddressTypeHandler.class)
private Address address;
复制代码

执行查询测试 image.png 输出的Address对象不再是空对象

执行插入测试 image.png 查看插入的数据 image.png 插入的address字段也不再是空。说明自定义的类型处理器生效

然后使用MyBatis全局配置文件注册AddressTypeHandler 给address属性增加@Column注解,让通用Mapper处理普通字段一样处理address 在全局配置文件中增加配置,settings标签下

<typeHandlers>
    <typeHandler handler="com.citi.handler.AddressTypeHandler"
                 javaType="com.citi.entity.Address" />
</typeHandlers>
复制代码

再次执行查询测试

image.png Address对象不为空

9.4 枚举类型的处理

9.4.1 将枚举类型当作简单类型来处理

配置enumAsSimpleType=true会把枚举类型当作简单类型处理,默认simpleType会忽略枚举类型,默认不处理,所以出现了一开始枚举内容为空的情况

在MyBatis全局配置文件中配置枚举类型处理的配置

<bean id="mapperScannerConfigurer" class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--其他被容不变-->
    <property name="properties">
        <value>
            enumAsSimpleType=true
        </value>
    </property>
</bean>
复制代码

执行查询测试

image.png 成功输出枚举类型的内容

执行插入测试

image.png 根据输出的SQL语句,插入时枚举内容不为空,可以查看数据库中插入的数据

image.png 枚举类型的内容也被成功插入到数据库中,说明配置生效。

9.4.2 为枚举类型配置对应的类型处理器

image.png

MyBatis内置了两种枚举类型的处理器

  • org.apache.ibatis.type.EnumTypeHandler
  • org.apache.ibatis.type.EnumOrdinalTypeHandler

使用EnumTypeHandler类型处理器

使用@ColumnType注解方式注册EnumTypeHandler处理器 image.png 编译阶段就已经报错,不能使用@Column注解注册EnumTypeHandler处理器

MyBatis全局配置文件中注册类型处理器,并在season属性上增加@Column注解

<typeHandlers>
    <typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"
                 javaType="com.citi.entity.SeasonEnum" />
</typeHandlers>
复制代码

增加@Column注解的作用是让通用Mapper不忽略枚举类型

执行查询测试

image.png 成功输出枚举类型的内容

执行插入测试

image.png

根据INSERT语句内容来看,插入的内容不为空,可以查看数据库插入的内容 image.png 成功将枚举内容插入到数据库中

使用EnumOrdinalTypeHandler类型处理器

枚举处理器中带Ordinal与不带Ordinal的区别:

  • 带Ordinal存的是索引值
  • 不带Ordinal存的是具体内容

在MyBatis全局配置文件中注册EnumOrdinalTypeHandler类型处理器

<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
             javaType="com.citi.entity.SeasonEnum" />
复制代码

执行插入测试

image.png

查看数据库

image.png

查询刚刚插入的数据

image.png

针对索引对应的内容非常大的时候比较适用

十、通用Mapper的配置项

通用Mapper的配置项配置位置在name=propertiesy的property标签下

<bean id="mapperScannerConfigurer" class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--指定接口所在的包-->
    <property name="basePackage" value="com.citi.mapper"></property>
    <property name="properties">
        <value>
            enumAsSimpleType=true
        </value>
    </property>
</bean>
复制代码

有多个配置项可以设置多个value标签。

通用 Mapper 提供了十几个配置参数,具体请参考通用Mapper配置

至此,通用Mapper部分完结✿✿ヽ(°▽°)ノ✿!

Guess you like

Origin juejin.im/post/7069414267476246535