八、通用Mapper的二级缓存
8.1 通用Mapper缓存测试
在PorscheMapperTest测试类中增加一个testCache测试方法
代码语言:javascript复制@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());
}
}
执行测试
调用了两次selectAll方法,控制台输出了两条SQL语句,说明并没有直接从缓存中提取数据
8.2 通用Mapper二级缓存配置
通用Mapper二级缓存的开启与原生MyBatis二级缓存的开启有相同的地方也有不同的地方
相同点:
都需要在MyBatis全局配置文件中开启二级缓存
都需要Entity实体类实现Serializable接口
不同点
- 原生MyBatis还需要在Mapper XML中的mapper标签下添加cache标签
- 通用Mapper由于没有Mapper XML,所以他的做法是在Mapper接口上增加@CacheNamespace,以实现同样的效果
原生MyBatis的一级缓存和二级缓存可以参考QA 由浅入深持久层框架(七)- MyBatis Cache
配置完成之后再次对PorscheMapperTest中的testCache方法执行测试
九、通用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表,并插入一条数据
代码语言:javascript复制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枚举类
代码语言:javascript复制@Data
@Table(name = "table_user")
public class User {
@Id
private Integer userId;
private String userName;
private Address address;
private SeasonEnum season;
}
代码语言:javascript复制@Data
public class Address {
private String province;
private String city;
private String street;
}
代码语言:javascript复制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接口
代码语言:javascript复制public interface UserMapper extends Mapper<User> {
}
新建service及impl包,增加UserService接口以及UserServiceImpl实现类,新增getUserById方法
代码语言:javascript复制public interface UserService {
User getUserById(Integer id);
}
代码语言:javascript复制@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getUserById(Integer id) {
return userMapper.selectByPrimaryKey(id);
}
}
在test包中新增UserServiceTest,对UserService中的getUserById方法进行测试
代码语言:javascript复制@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);
}
}
执行测试
输出的User对象address属性和season属性都是空的
这是因为通用Mapper默认把复杂类型Address和SeasonEnum忽略掉了,默认只处理简单类型
再来试试insert方法,在UserServiceTest中增加saveUser方法的测试
代码语言:javascript复制@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);
}
执行测试
INSERT语句中address字段和season字段的值是null
通用Mapper默认情况下会忽略复杂类型,对复杂类型不进行“从类到表”的映射
9.3 自定义类型处理器TypeHandler
以上问题的解决方式有两种。第一种是新建一张address表,建立user表到address表的关联关系,在MyBatis Mapper XML中使用resultMap和collection标签重新定义映射关系
第二种方式是不创建新的表,就将Address属性的内容全部存到表的address字段中,这就需要使用到自定义的类型处理器
自定义类型处理器要注意字段存储的内容为字符串,所以自定义的类型处理器的主要功能是建立一个规则,将address属性转化为字符串存储在数据库中,并按照一定的格式存储,这个规则还包括查询时,将字符串转化为实体类类型。
查看类型转换的顶级接口TypeHandler以及BaseTypeHandler
- setNonNullParameter:将要做类型处理的parameter对象转换为字符串存在ps对象的i位置
- getNullableResult:从结果集中获取查询结果转换为原始对象
可以通过继承BaseTypeHandler实现自定义的类型处理器
9.3.1 实现自定义类型处理器AddressTypeHandler
新建一个类AddressTypeHandler继承BaseTypeHandler
代码语言:javascript复制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注解注册
代码语言:javascript复制@ColumnType(typeHandler = AddressTypeHandler.class)
private Address address;
执行查询测试
输出的Address对象不再是空对象
执行插入测试
查看插入的数据
插入的address字段也不再是空。说明自定义的类型处理器生效
然后使用MyBatis全局配置文件注册AddressTypeHandler 给address属性增加@Column注解,让通用Mapper处理普通字段一样处理address 在全局配置文件中增加配置,settings标签下
代码语言:javascript复制<typeHandlers>
<typeHandler handler="com.citi.handler.AddressTypeHandler"
javaType="com.citi.entity.Address" />
</typeHandlers>
再次执行查询测试
Address对象不为空
9.4 枚举类型的处理
9.4.1 将枚举类型当作简单类型来处理
配置enumAsSimpleType=true会把枚举类型当作简单类型处理,默认simpleType会忽略枚举类型,默认不处理,所以出现了一开始枚举内容为空的情况
在MyBatis全局配置文件中配置枚举类型处理的配置
代码语言:javascript复制<bean id="mapperScannerConfigurer" class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<!--其他被容不变-->
<property name="properties">
<value>
enumAsSimpleType=true
</value>
</property>
</bean>
执行查询测试
成功输出枚举类型的内容
执行插入测试
根据输出的SQL语句,插入时枚举内容不为空,可以查看数据库中插入的数据
枚举类型的内容也被成功插入到数据库中,说明配置生效。
9.4.2 为枚举类型配置对应的类型处理器
MyBatis内置了两种枚举类型的处理器
- org.apache.ibatis.type.EnumTypeHandler
- org.apache.ibatis.type.EnumOrdinalTypeHandler
使用EnumTypeHandler类型处理器
使用@ColumnType注解方式注册EnumTypeHandler处理器
编译阶段就已经报错,不能使用@Column注解注册EnumTypeHandler处理器
MyBatis全局配置文件中注册类型处理器,并在season属性上增加@Column注解
代码语言:javascript复制<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"
javaType="com.citi.entity.SeasonEnum" />
</typeHandlers>
增加@Column注解的作用是让通用Mapper不忽略枚举类型
执行查询测试
成功输出枚举类型的内容
执行插入测试
根据INSERT语句内容来看,插入的内容不为空,可以查看数据库插入的内容
成功将枚举内容插入到数据库中
使用EnumOrdinalTypeHandler类型处理器
枚举处理器中带Ordinal与不带Ordinal的区别:
- 带Ordinal存的是索引值
- 不带Ordinal存的是具体内容
在MyBatis全局配置文件中注册EnumOrdinalTypeHandler类型处理器
代码语言:javascript复制<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.citi.entity.SeasonEnum" />
执行插入测试
查看数据库
查询刚刚插入的数据
针对索引对应的内容非常大的时候比较适用
十、通用Mapper的配置项
通用Mapper的配置项配置位置在name=propertiesy的property标签下
代码语言:javascript复制<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部分完结✿✿ヽ(°▽°)ノ✿!