1.通用Mapper逆向工程
1.1与原生MyBatis的逆向工程对比
也就是通用Mapper生成的更加详细简单
1.2逆向工程的实现
- 创建新工程,然后在pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.njit</groupId>
<artifactId>mapper-mbg</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mapper-mbg</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<!-- ${basedir}引用工程根目录 -->
<!-- targetJavaProject:声明存放源码的目录位置 -->
<targetJavaProject>${basedir}/src/main/java</targetJavaProject>
<!-- targetMapperPackage:声明MBG生成XxxMapper接口后存放的package位置 -->
<targetMapperPackage>com.njit.mappermbg.mappers</targetMapperPackage>
<!-- targetModelPackage:声明MBG生成实体类后存放的package位置 -->
<targetModelPackage>com.njit.mappermbg.entity</targetModelPackage>
<!-- targetResourcesProject:声明存放资源文件和XML配置文件的目录位置 -->
<targetResourcesProject>${basedir}/src/main/resources</targetResourcesProject>
<!-- targetXMLPackage:声明存放具体XxxMapper.xml文件的目录位置 -->
<targetXMLPackage>mappers</targetXMLPackage>
<!-- 通用Mapper的版本号 -->
<mapper.version>4.1.5</mapper.version>
<!-- MySQL驱动版本号 -->
<mysql.version>8.0.20</mysql.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!--逆向工程插件-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<!-- 配置generatorConfig.xml配置文件的路径 -->
<configuration>
<configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
<!-- MBG插件的依赖信息 -->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>${mapper.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
- 在application.peoperties中配置数据库连接的属性
# Database connection information
jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/tk_mapper
jdbc.user = root
jdbc.password = root
# mapper
mapper.plugin = tk.mybatis.mapper.generator.MapperPlugin
mapper.Mapper = tk.mybatis.mapper.common.Mapper
- 然后写generatorConfig.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 引入外部属性文件 -->
<properties resource="application.properties" />
<context id="Mysql" targetRuntime="MyBatis3Simple"
defaultModelType="flat">
<property name="beginningDelimiter" value="`" />
<property name="endingDelimiter" value="`" />
<!-- 配置通用Mapper的MBG插件相关信息 -->
<plugin type="${mapper.plugin}">
<property name="mappers" value="${mapper.Mapper}" />
</plugin>
<!-- 配置连接数据库的基本信息 -->
<jdbcConnection
driverClass="${jdbc.driverClass}"
connectionURL="${jdbc.url}"
userId="${jdbc.user}"
password="${jdbc.password}">
</jdbcConnection>
<!-- 配置Java实体类存放位置 -->
<javaModelGenerator
targetPackage="${targetModelPackage}"
targetProject="${targetJavaProject}" />
<!-- 配置XxxMapper.xml存放位置 -->
<sqlMapGenerator
targetPackage="${targetXMLPackage}"
targetProject="${targetResourcesProject}" />
<!-- 配置XxxMapper.java存放位置 -->
<javaClientGenerator
targetPackage="${targetMapperPackage}"
targetProject="${targetJavaProject}"
type="XMLMAPPER" />
<!-- 根据数据库表生成Java文件的相关规则 -->
<!-- tableName="%"表示数据库中所有表都参与逆向工程,此时使用默认规则 -->
<!-- 默认规则:table_dept→TableDept -->
<!-- 注意这里有几张表就要写几个table标签-->
<!-- 不符合默认规则时需要使用tableName和domainObjectName两个属性明确指定 -->
<table tableName="table_emp" domainObjectName="Employee">
<!-- 配置主键生成策略 -->
<generatedKey column="emp_id" sqlStatement="Mysql" identity="true" />
</table>
<table tableName="user">
<!-- 配置主键生成策略 -->
<generatedKey column="id" sqlStatement="Mysql" identity="true" />
</table>
</context>
</generatorConfiguration>
- 运行测试
右键项目,选择run build...,然后在参数上写mybatis-generator:generate
, 如果不是在集成开发环境就要使用 mvn mybatis-generator:generate
命令
如果是在idea中,则按照如下图,置成功后maven中的Plugins会出现mybatis-generator,双击mybatis-generator目录下的mybatis-generator:generate,就会生成实体类和对应的mapper接口以及xml文件
- 查看结果
运行后可以看到
然后检查可以发现已经帮我们生成对应的实体类,接口和mapper文件。
2.自定义mapper<T>接口
只需要自定义一个自己的接口,然后使用注解注册,最后自己的mapper继承这个接口就行
import tk.mybatis.mapper.additional.idlist.IdListMapper;
import tk.mybatis.mapper.additional.insert.InsertListMapper;
import tk.mybatis.mapper.annotation.RegisterMapper;
@RegisterMapper
public interface MyMapper<T> extends Mapper<T>, IdListMapper<T, Long>, InsertListMapper<T> {
}
4.0 版本提供的所有通用接口上都标记了该注解,因此自带的通用接口时,不需要配置
mappers
参数
如果是和spring整合的时候需要这样配
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.njit.mapper.mappers"/>
<property name="properties">
<value>
mappers=com.njitzyd.mapper.mine_mappers.MyMapper
</value>
</property>
</bean>
3.通用 Mapper 接口扩展
- 完整的实现代码如下
# step1:先建一个扩展的接口
public interface MyBatchUpdateMapper<T> {
@UpdateProvider(
type = MyBatchUpdateProvider.class,
method = "dynamicSQL")
void batchUpdateMapper(List<T> list);
}
# step2;然后写一个同名的实现的方法,注意这两个之间没有任何的继承或者实现的关系,只是类和接口中的方法名需要一致
public class MyBatchUpdateProvider extends MapperTemplate {
public MyBatchUpdateProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
}
// 实现方式也就是拼接sql语句
/*
<foreach collection="list" item="record" separator=";" >
UPDATE table_emp
<set>
emp_name=#{record.empName},
emp_age=#{record.empAge},
emp_salary=#{record.empSalary},
</set>
where emp_id=#{record.empId}
</foreach>
*/
public String batchUpdateMapper(MappedStatement statement){
//1.创建StringBuilder用于拼接SQL语句的各个组成部分
StringBuilder builder = new StringBuilder();
//2.拼接foreach标签
builder.append("<foreach collection=\"list\" item=\"record\" separator=\";\" >");
//3.获取实体类对应的Class对象
Class<?> entityClass = super.getEntityClass(statement);
//4.获取实体类在数据库中对应的表名
String tableName = super.tableName(entityClass);
//5.生成update子句
String updateClause = SqlHelper.updateTable(entityClass, tableName);
builder.append(updateClause);
builder.append("<set>");
//6.获取所有字段信息
Set<EntityColumn> columns = EntityHelper.getColumns(entityClass);
String idColumn = null;
String idHolder = null;
for (EntityColumn entityColumn : columns) {
boolean isPrimaryKey = entityColumn.isId();
//7.判断当前字段是否为主键
if(isPrimaryKey) {
//8.缓存主键的字段名和字段值
idColumn = entityColumn.getColumn();
//※返回格式如:#{record.age,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
idHolder = entityColumn.getColumnHolder("record");
}else {
//9.使用非主键字段拼接SET子句
String column = entityColumn.getColumn();
String columnHolder = entityColumn.getColumnHolder("record");
builder.append(column).append("=").append(columnHolder).append(",");
}
}
builder.append("</set>");
//10.使用前面缓存的主键名、主键值拼接where子句
builder.append("where ").append(idColumn).append("=").append(idHolder);
builder.append("</foreach>");
//11.将拼接好的字符串返回
return builder.toString();
}
}
# step3;然后写一个自己的通用的Mapper,注意在springboot中需要在自定义的通用mapper上添加这个注解
@RegisterMapper
public interface MyMapper<T> extends MyBatchUpdateMapper{
}
# 最后在自己的Mapper中继承自己的定义的通用Mapper
public interface EmployeeMapper extends Mapper<Employee>,MyMapper<Employee> {
}
注意点:在运行时会报错,因为MySQL要是这种批量的操作,也就是使用;
进行隔离的sql语句,需要在数据库连接后面添加上allowMultiQueries=true
,这样才支持批量的SQL语句。
- 测试
#测试方法
@SpringBootTest
@RunWith(SpringRunner.class)
class MapperMbgApplicationTests {
@Autowired
private EmployeeMapperService employeeMapperService;
#测试批量更新
@Test
void testBatchUpdate() {
List<Employee> list = new ArrayList<>();
Employee e1= new Employee(2,"new",400.0,20);
Employee e2= new Employee(3,"new",400.0,20);
list.add(e1);
list.add(e2);
employeeMapperService.batchUpdate(list);
list.forEach(System.out::println);
}
}
# Service中的实现方式
@Service
public class EmployeeMapperService {
@Autowired
private EmployeeMapper employeeMapper;
/**
* @Author zhuyoude
* @Description //测试自己扩展的批量更新
* @Date 2020/5/21 12:07
* @Param [list]
* @return void
**/
public void batchUpdate(List<Employee> list) {
employeeMapper.batchUpdateMapper(list);
}
}
-
结果
在这里插入图片描述
原始数据如图,测试后刷新可以看到如下图:
发现数据已经批量更新成功!
4.二级缓存
首先需要开启缓存,默认的二级缓存是没开启的。开启缓存就是开启mybatis的缓存。
在配置文件中配置:
mybatis.configuration.cache-enabled=true
下面就要分两种情况,一种只有mapper接口,另一种是有mapper接口和mapper的xml文件。
- 只用mapper接口
只需要在接口上添加注解@CacheNamespace
,然后他是要求实体类需要序列化的,所以再让实体类实现Serializable
@CacheNamespace
public interface EmployeeMapper extends Mapper<Employee>,MyMapper<Employee> {
}
这样就开启这个mapper的二级缓存。测试两次相同的查询只会到数据库中查询一次。
- mapper接口和mapper的xml文件同时存在(参考官网)
在 XML 中定义缓存:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="tk.mybatis.mapper.cache.CountryCacheRefMapper">
<cache/>
<select id="selectById" resultType="tk.mybatis.mapper.base.Country">
select * from country where id = #{id}
</select>
</mapper>
在接口中配置注解引用:
@CacheNamespaceRef(CountryCacheRefMapper.class)
//或者 @CacheNamespaceRef(name = "tk.mybatis.mapper.cache.CountryCacheRefMapper")
public interface CountryCacheRefMapper extends Mapper<Country> {
/**
* 定义在 XML 中的方法
*
* @param id
* @return
*/
Country selectById(Integer id);
}
总结:其实二级缓存在真实的项目中使用的并不多,大多情况还是使用像redis这样的非关系型数据库来充当缓存。
5类型处理器:TypeHandler
5.1基本数据类型与复杂数据类型
基本数据类型 | byte short int long double float char boolean |
---|---|
引用数据类型 | 类、 接口、 数组、 枚举…… |
简单类型 | 只有一个值的类型 |
复杂类型 | 有多个值的类型 |
通用 Mapper 默认情况下会忽略复杂类型, 对复杂类型不进行“从类到表” 的映射。
5.2TypeHandler接口
public interface TypeHandler<T> {
//将 parameter 设置到 ps 对象中, 位置是 i
//在这个方法中将 parameter 转换为字符串
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws
SQLException;
//根据列名从 ResultSet 中获取数据, 通常是字符串形式
//将字符串还原为 Java 对象, 以 T 类型返回
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
5.3BaseTypeHandler 类中的抽象方法说明
//将 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;
5.4自定义类型转换器类
- 重新建一下用户表,然后添加测试数据
create table user (
id integer NOT NULL AUTO_INCREMENT ,
name varchar(32),
address varchar(64),
PRIMARY KEY (`id`)
);
INSERT INTO user (id, name, address) VALUES (1, 'abel533', 'JiangSu/NanJing');
INSERT INTO user (id, name, address) VALUES (2, 'isea533', 'JiangSu/XuZhou');
- 实体类
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private Address address;
// 省略getter和setter
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
private String province;
private String city;
}
- Mapper接口
public interface UserMapper extends Mapper<User> {
}
- 编写自定义的类型转换器
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 province = address.getProvince();
String city = address.getCity();
//3.拼装成一个字符串
//规则:各个值之间使用“,”分开
StringBuilder builder = new StringBuilder();
builder
.append(province)
.append(",")
.append(city)
.append(",");
String parameterValue = builder.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需要的具体数据
String province = split[0];
String city = split[1];
//5.根据具体对象组装一个Address对象
Address address = new Address(province, city);
return address;
}
@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需要的具体数据
String province = split[0];
String city = split[1];
//5.根据具体对象组装一个Address对象
Address address = new Address(province, city);
return address;
}
@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需要的具体数据
String province = split[0];
String city = split[1];
//5.根据具体对象组装一个Address对象
Address address = new Address(province, city);
return address;
}
}
- 在类的属性上添加注解
@ColumnType(typeHandler = AddressTypeHandler.class)
@ColumnType(typeHandler = AddressTypeHandler.class)
private Address address;
- 测试查询
@Test
void testHandler(){
User user = userMapper.selectByPrimaryKey(2);
System.out.println(user);
}
可以看到Address有值:
-
如果是配置全局有效,则在实体类的字段上添加
@Cloumn
注解,然后在配置文件中配置@Column private Address address;
# 配置handler所在的包 mybatis.type-handlers-package=com.njit.mappermbg.handler
这样再次测试会发现Address会有值!
5.5枚举类型
默认枚举类型也是忽略不存储的,枚举类型如何存储到数据库中,有两种方式!
- 方式一:让通用 Mapper 把枚举类型作为简单类型处理
只需要在配置文件中配置就可以!这三者选择其一就可以了!
#其本质是使用了 org.apache.ibatis.type.EnumTypeHandler<E>
mapper.enum-as-simple-type=true
#或者指定mybatis的处理方式,在这种方式存放的是枚举的值
mybatis.configuration.default-enum-type-handler=org.apache.ibatis.type.EnumTypeHandler
#这种方式存放的是枚举的索引
mybatis.configuration.default-enum-type-handler=org.apache.ibatis.type.EnumOrdinalTypeHandler
-
方式二,想9.4那样自定义一个类型转换器(这里不再展示,实现方式相似),或者自己写一个Handler继承
EnumTypeHandler
继承的原因是
EnumTypeHandler
是带有泛型的,而使用@ColumnType 注解中的typeHandler
参数的值是不能接收带泛型的,所以需要如下这样完成public class StateEnumTypeHandler extends EnumOrdinalTypeHandler<StateEnum> { public StateEnumTypeHandler(Class<StateEnum> type) { super(type); } } # 然后在实体类的属性上添加注解 @ColumnType(typeHandler = StateEnumTypeHandler.class) private StateEnum state;