一、简单类型与复杂类型
1、三个类型
User类:
@Table(name = "table_user")
public class User {
@Id
private Integer userId;
private String userName;
private Address address;
private SeasonEnum season;
public User() {
}
public User(Integer userId, String userName, Address address, SeasonEnum season) {
this.userId = userId;
this.userName = userName;
this.address = address;
this.season = season;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public SeasonEnum getSeason() {
return season;
}
public void setSeason(SeasonEnum season) {
this.season = season;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", address=" + address +
", season=" + season +
'}';
}
}
Address类:
public class Address {
private String provice;
private String city;
private String stree;
public Address() {
}
public Address(String provice, String city, String stree) {
this.provice = provice;
this.city = city;
this.stree = stree;
}
public String getProvice() {
return provice;
}
public void setProvice(String provice) {
this.provice = provice;
}
public String getCity() {
return city;
}
@Override
public String toString() {
return "Address{" +
"provice='" + provice + '\'' +
", city='" + city + '\'' +
", stree='" + stree + '\'' +
'}';
}
public void setCity(String city) {
this.city = city;
}
public String getStree() {
return stree;
}
public void setStree(String stree) {
this.stree = stree;
}
}
SeasonEnum 类:
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 seasonName;
}
public void setSeasonName(String seasonName) {
this.seasonName = seasonName;
}
}
对于上面的三个类,SeasonEnum 是一个枚举类型,Address 是具有三个 String 类型的引用类型,User 具有两个 String 类型,一个Address 类型和一个枚举类型的复杂类型。
而通用Mapper进行增删改查是只能用于简单类型,对于复杂类型是不支持的。
2、简单类型和复杂类型
基本数据类型:byte、short、int、long、double、float、char、boolean
引用数据类型:类、接口、数组、枚举……
简单类型:只有一个值的类型
复杂类型:多个简单类型组合起来,有多个值的类型
3、通用Mapper处理基本类型与复杂类型
当使用通用Mapper 对 User 类进行查询时,根据查询结果看到,通用Mapper只会处理简单数据类型,忽略复杂数据类型。
注意:通用Mapper默认情况下会忽略复杂类型,对复杂类型不进行从“从类到表”的映射
4、自定义类型处理器
在实际开发中,如果需要把复杂类型也存储到数据库中,
方式一:使用关联的管理,使用多表进行关联;
方式二:把复杂类型整体作为一个字段存储到数据库中,使用类型处理器;
5、BaseTypeHandler
TypeHandler 接口
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
BaseTypeHandler 抽象类:
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
....其他方法
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
}
继承树:
可以看到通用 Mapper 中的类型处理器都继承自 BaseTypeHandler,所以自定义类型处理器时,我们也继承 BaseTypeHandler ,然后重写其中的抽象方法即可。
抽象方法说明:
//将parameter对象转换为字符串存入到 ps 对象的i位置
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
//从结果集中获取数据库对应查询结果
//将字符串还原为原始的T类型对象
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
二、Address 类型处理器
1、创建 AddressTypeHandler 类型处理器
2、继承 BaseTypeHandler 并实现抽象方法
public class AddressTypeHandler extends BaseTypeHandler<Address> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Address address, JdbcType jdbcType) throws SQLException {
//1. 对 address 对象进行验证
if (address == null) {
return;
}
//2. 从 Address 对象中取出具体数据
String provice = address.getProvice();
String city = address.getCity();
String stree = address.getStree();
//3. 拼装成一个字符串
//规则:各个值之间使用 "," 分开
StringJoiner joiner = new StringJoiner(",");
joiner.add(provice).add(city).add(stree);
String parameterValue = joiner.toString();
//4. 设置参数
ps.setString(i, parameterValue);
}
@Override
public Address getNullableResult(ResultSet rs, String columnName) throws SQLException {
//1. 从结果集根据字段名从rs对象中获取字段值
String columnValue = rs.getString(columnName);
//2. 验证 columnValue 是否有效
if (columnValue == null || columnValue.length() == 0 || !columnValue.contains(",")) {
return null;
}
//3. 根据","对 columnValue 进行拆分
String[] split = columnValue.split(",");
//4.从拆分结果数组中获取 Address 需要的具体数据
return new Address(split[0], split[1], split[2]);
}
@Override
public Address getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
//1. 从结果集根据字段名从rs对象中获取字段值
String columnValue = rs.getString(columnIndex);
//2. 验证 columnValue 是否有效
if (columnValue == null || columnValue.length() == 0 || !columnValue.contains(",")) {
return null;
}
//3. 根据","对 columnValue 进行拆分
String[] split = columnValue.split(",");
//4.从拆分结果数组中获取 Address 需要的具体数据
return new Address(split[0], split[1], split[2]);
}
@Override
public Address getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
//1. 从结果集根据字段名从rs对象中获取字段值
String columnValue = cs.getString(columnIndex);
//2. 验证 columnValue 是否有效
if (columnValue == null || columnValue.length() == 0 || !columnValue.contains(",")) {
return null;
}
//3. 根据","对 columnValue 进行拆分
String[] split = columnValue.split(",");
//4.从拆分结果数组中获取 Address 需要的具体数据
return new Address(split[0], split[1], split[2]);
}
}
3、注册自定义类型处理器 AddressTypeHandler
方式一:字段级别,使用 @ColumnType 注解
@ColumnType(typeHandler = AddressTypeHandler.class)
private Address address;
方式二:全局级别,在 MyBatis 配置文件中配置 typeHandlers
① 在要处理的属性上面添加@Column 注解,让通用Mapper把该字段作为一个列处理
@Column
private Address address;
② 在全局配置文件中配置 类型处理器
<!--配置类型转换器-->
<typeHandlers>
<!--
handler属性:指定自定义类型转换器全类名
javaType属性:指定需要使用“自定义类型转换器”进行类型处理的实体类型
-->
<typeHandler handler="com.njf.mapper.typeHandler.AddressTypeHandler" javaType="com.njf.mapper.bean.Address"/>
</typeHandlers>
4、测试
设置完毕后,address 可以正常处理。
三、枚举类型处理器
1、方式一:让通用Mapper把枚举类型作为简单类型处理
增加一个通用Mapper的配置项
在Spring配置文件中找到 MapperScannerConfigurer,用于配置是否将枚举类型当成基本类型对待。
默认 simpleType 会忽略枚举类型,使用 enumAsSimpleType 配置后悔把枚举按简单类型处理,需要自己配置好 typeHandler。
配置方式如下:
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.njf.mapper.mappers"/>
<property name="properties">
<value>
enumAsSimpleType=true
</value>
</property>
</bean>
本质就是使用 EnumTypeHandler 处理器
2、方式二:让枚举类型配置对应的类型处理器
(1)枚举类型处理器
内置处理器:通用Mapper 定义类内置两个枚举类型处理器:
org.apache.ibatis.type.EnumTypeHandler
org.apache.ibatis.type.EnumOrdinalTypeHandler
自定义处理器:我们也可以自定义枚举类型处理器。
(2)处理器如何注册
自定义类型处理器注册方式同上。
内置类型处理器注册:
方式一:使用@ColumnType 注解(失败)
在枚举类型这里无法使用 @ColumnType 注解注册 MyBatis 内置的枚举类型处理器
//在枚举类型这里无法使用 @ColumnType 注解注册 MyBatis 内置的枚举类型处理器
@ColumnType(typeHandler = EnumTypeHandler.class)
private SeasonEnum season;
报错信息:
方式二:配置文件中进行配置
① 在字段上使用 @Column 注解
注意:加@Column 注解的作用是让通用 Mapper 不忽略枚举类型。
@Column
private SeasonEnum season;
② 在全局配置文件中进行配置
<!--配置类型转换器-->
<typeHandlers>
<!--
handler属性:指定自定义类型转换器全类名
javaType属性:指定需要使用“自定义类型转换器”进行类型处理的实体类型
-->
<typeHandler handler="com.njf.mapper.typeHandler.AddressTypeHandler" javaType="com.njf.mapper.bean.Address"/>
<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler" javaType="com.njf.mapper.bean.SeasonEnum"/>
</typeHandlers>
需要在 mybatis 的配置文件中配置专门的类型处理器,并在字段上使用 @Column 注解
3、测试
可以正常使用了。
扩展:是否带 Ordinal
EnumTypeHandler:在数据库中存储枚举值本身
EnumOrdinalTypeHandler:在数据库中仅仅存储枚举值索引
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?