MyBatis之通用Mapper教程《二》高级教程

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接口

只需要自定义一个自己的接口,然后使用注解注册,最后自己的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;
    

6. 文档源码地址

https://gitee.com/njitzyd/tk-mapper

7. 上一篇

MyBatis之通用Mapper教程《一》基本使用.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值