使用通用Mapper
的目的是为了替我们生成常用增删改查操作的SQL
语句,并能够简化对于Mybatis
的操作。
一、快速入门 1.1 数据库表的创建 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 CREATE TABLE `tabple_emp` ( `emp_id` INT NOT NULL AUTO_INCREMENT, `emp_name` VARCHAR ( 500 ) NULL, `emp_salary` DOUBLE ( 15, 5 ) NULL, `emp_age` INT NULL, PRIMARY KEY ( `emp_id` ) ); INSERT INTO `tabple_emp` ( `emp_name`, `emp_salary`, `emp_age` ) VALUES ( 'tom', '1254.37', '27' ); INSERT INTO `tabple_emp` ( `emp_name`, `emp_salary`, `emp_age` ) VALUES ( 'jerry', '6635.42', '38' ); INSERT INTO `tabple_emp` ( `emp_name`, `emp_salary`, `emp_age` ) VALUES ( 'bob', '5560.11', '40' ); INSERT INTO `tabple_emp` ( `emp_name`, `emp_salary`, `emp_age` ) VALUES ( 'kate', '2209.11', '22' ); INSERT INTO `tabple_emp` ( `emp_name`, `emp_salary`, `emp_age` ) VALUES ( 'justin', '4203.15', '30' );
1.2 对应实体类的创建 基本数据类型在Java
类中都有默认值,会导致Mybatis
在执行相关操作时很难判断当前字段是否为Null
。因此,在Mybatis
环境下使用Java
实体类时尽量不要使用基本数据类型,都使用对应的包装类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Employee implements Serializable { private Integer empId; private String empName; private Double empSalary; private Integer empAge; public Employee () { } public Employee (Integer empId, String empName, Double empSalary, Integer empAge) { this .empId = empId; this .empName = empName; this .empSalary = empSalary; this .empAge = empAge; } }
1.3 Spring-SpringMVC-Mybatis的整合 整合步骤见此文ssm框架的整合 。
二、通用Mapper的MBG 原生的MBG
和通用的MBG
做对比。
通用Mapper
的逆向工程,通过其特点的插件,同样的生成Java
实体类对象,带有注解(@Id
、@Column
等注解);在dao
接口层,即mapper
接口继承通用Mapper
中核心的接口Mapper<T>
;生成的实体类Mapper
文件(XXxMapper
文件)没有SQL
语句标签。
当通用Mapper
与Spring
或SpringBoot
整合完以后,通用Mapper
的MBG
可参考官方文档 使用Maven
执行MBG
的方式。
2.1 自定义Mapper
接口
其自己的Mapper<T>
接口层次结构如上所示。
作用,根据我们自身的需要,继承上方的层级结构中的mapper
接口,供我们自身开发。
举例:
自定义接口:
自定义的Mapper不能和原有的实体类Mapper放在同一级的目录下。
1 2 public interface MyInterface <T > extends BaseMapper <T >, ExampleMapper <T > {}
1 2 3 @Repository public interface EmployeeMapper extends MyMapper <Employee > {}
配置MapperScannerConfigurer
注册MyMapper<T>
,或者在我们自定义的Mapper
接口中加入注解@RegisterMapper
1 2 3 4 5 6 7 8 9 10 !-- 配置扫描器,将mybatis接口的实现加入到ioc容器中 --> <bean class ="tk.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="cn.lizhi.dao" > </property > <property name ="properties" > <value > mapper=cn.lizhi.myInterface.MyMapper </value > </property > </bean >
其中value
值默认的是原生mapper
的值。
2.2 通用Mapper接口扩展 其扩展用来指增加通用Mapper中没有提供的功能。
示例:批量更新。
思路:当我们写SQL
语句时,如何能做到批量更新呢?即用;
分割我们需要更新的SQL
语句。
1 2 3 4 5 UPDATE table_emp SET emp_name=?,emp_age=?,emp_salary=? WHERE emp_id=?;UPDATE table_emp SET emp_name=?,emp_age=?,emp_salary=? WHERE emp_id=?;UPDATE table_emp SET emp_name=?,emp_age=?,emp_salary=? WHERE emp_id=?;UPDATE table_emp SET emp_name=?,emp_age=?,emp_salary=? WHERE emp_id=?;...
那么Mybatis
又是如何做到上面这种形式的呢?即,通过foreach
标签达到语句的拼接。
1 2 3 4 5 6 7 8 <foreach collection='list' item='record' separator=';' > UPDATE table_emp SET emp_name=#{record.empName}, emp_age=#{record.empAge}, emp_salay=#{record.empSalary} WhERE emp_id=#{record.empId} </foreach>
即我们需要使用通用Mapper能够做到动态的生成上面的SQL
语句,供我们使用,即可做到接口的扩展。
2.2.1 需要提供的接口和实现类
在我们自定义的MyMapper<T>
接口中除了需要继承Mapper<T>
中下方层次结构的接口,它还需要继承我们自己自定义功能的Mapper
接口,这里是MyBatchUpdateProvider
。
其中MyBatchUpdateProvider
是我们自己编写的类(需要继承模板),用于解析xml
的SQL
语句。
代码示例:
首先编写我们自定义的接口MyBatchUpdateMapper
。
1 2 3 4 5 6 @RegisterMapper public interface MyBatchUpdateMapper <T > { @UpdateProvider (type=MyBatchUpdateProvider.class , method ="dynamicSQL" ) void batchUpdateMapper (List<T> list) ; }
这里的batchUpdateMapper
就是我们后续需要生成模板代码的方法。
编写MyBatchUpdateProvider
类,继承MapperTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class MyBatchUpdateProvider extends MapperTemplate { public MyBatchUpdateProvider (Class<?> mapperClass, MapperHelper mapperHelper) { super (mapperClass, mapperHelper); } public String batchUpdateMapper (MappedStatement ms) { final Class<?> entityClass = super .getEntityClass(ms); final String tableName = super .tableName(entityClass); super .setResultType(ms, entityClass); StringBuilder sql = new StringBuilder(); sql.append("<foreach collection='list' item='record' separator=';'>" ); String updateClause = SqlHelper.updateTable(entityClass, tableName); sql.append(updateClause); sql.append("<set>" ); Set<EntityColumn> columns = EntityHelper.getColumns(entityClass); String Id_column = null ; String Id_columnHolder = null ; for (EntityColumn entityColumn : columns) { boolean flag = entityColumn.isId(); if (flag) { Id_column = entityColumn.getColumn(); Id_columnHolder = entityColumn.getColumnHolder("record" ); } else { String column = entityColumn.getColumn(); String columnHolder = entityColumn.getColumnHolder("record" ); sql.append(column).append("=" ).append(columnHolder).append("," ); } } sql.append("</set>" ); sql.append("where " ).append(Id_column).append("=" ).append(Id_columnHolder); sql.append("</foreach>" ); return sql.toString(); } }
这里通用代码编写的方法要和我们前面接口中定义的方法名相同,这个方法就是最后我们使用接口时,需要使用的方法。
最后,编写我们自定义的Mapper
。
1 2 3 @RegisterMapper public interface MyMapper <T > extends Mapper <T >,MyBatchUpdateMapper <T > {}
在使用时,我们实体类Mapper接口中的用法为:
1 2 3 4 @Repository public interface EmployeeMapper extends MyMapper <Employee > {}
即,只需要继承我们自定义的Mapper即可。
测试类编写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @RunWith (SpringJUnit4ClassRunner.class ) @ContextConfiguration (locations = "classpath:applicationContext.xml" )public class MapperTest { @Autowired private EmployeeService employeeService; @Test public void batchUpdateEmployeeTest () { List<Employee> list = new ArrayList<Employee>(); Employee emp01 = new Employee(1 , "小明" , 120000 d, 18 ); Employee emp02 = new Employee(2 , "小红" , 130000 d, 19 ); Employee emp03 = new Employee(3 , "小黑" , 140000 d, 20 ); Employee emp04 = new Employee(4 , "小娜" , 150000 d, 21 ); list.add(emp01); list.add(emp02); list.add(emp03); list.add(emp04); employeeService.batchUpdateEmployee(list); } }
主要需要在dbConfig.xml
中的url
里配置上批量查询的请求参数,即:
1 jdbc.url =jdbc:mysql://localhost:3306/mybatis_mapper?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
2.3 通用Mapper的二级缓存方式 对同一内容查询两次,其查询两次数据库,默认并没有将第一次查询的内容进行缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void findAll () { List<Employee> employees = employeeService.findAll(); for (Employee employee : employees) { System.out.println(employee); } System.out.println("----" ); List<Employee> employeeList = employeeService.findAll(); for (Employee employee : employeeList) { System.out.println(employee); } }
加入二级缓存方式:
在Mybatis
全局配置文件mybatis-config.xml
中开启二级缓存。
1 2 3 4 5 6 7 8 9 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <settings > <setting name ="cacheEnabled" value ="true" /> </settings > </configuration >
实体类的Mapper接口加入@CacheNamespace
注解
1 2 3 4 @Repository @CacheNamespace public interface EmployeeMapper extends MyMapper <Employee > {}
2.4 实体类中含有复杂类型的注入 2.4.1 简单类型和复杂类型
基本数据类型:byte、char、short、int、float、double、boolean
引用类型:类、接口、数据、枚举...
简单类型:只有一个值的类型
复杂类型:多个简单类型组合起来
2.4.2 准备工作 —— 相关类的创建 创建复杂类型的类。即创建一张表table_user
,表的每个字段对应下面User
实体类的属性,并没有进行主从表的建设,而是直接使用一张表进行操作。其对应的实体类如下:
1 2 3 4 5 6 7 8 9 10 11 12 @Table (name="table_user" )public class User { @Id @Column (name = "user_id" ) private Integer userId; private String userName; private Address address; private SeasonEnum season; }
Address
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Address { private String province; private String city; private String street; public Address () { } public Address (String province, String city, String street) { this .province = province; this .city = city; this .street = street; } }
SeasonEnum
类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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; } }
数据库表的建立:
1 2 3 4 5 6 7 8 DROP TABLE if EXISTS table_user; CREATE TABLE table_user( user_id INT NOT NULL AUTO_INCREMENT, user_name VARCHAR(32) NULL, address VARCHAR(32) NULL, season ENUM("summer @_@","spring @_@","autumn @_@","winter @_@") NULL, PRIMARY KEY (user_id) )
当使用通用mapper
对其进行表的查询时,例如:
1 2 3 4 5 6 @Test public void testQueryUser () { Integer userId = 1 ; User user = userService.findById(userId); System.out.println(user); }
返回结果:
1 User [userId=1 , userName=Justin, address=null , season=null ]
自动忽略复杂类型的属性注入。对复杂类型不进行”从类到表”的映射。
解决办法:采用typeHandler
。设定一种规则,实现复杂类型中的字段和实体类属性的映射。即自定义类型转换器。这里举例,针对Address
对象。
首先顶级接口:TypeHandler
,其实现接口为:
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T>
是一个抽象类,其抽象方法:
1 2 3 4 5 6 7 8 9 10 11 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 ;
2.4.3 自定义类型处理器的编写 接下来编写AddressHandler
转换器的编写 —— 各个值之间使用,
分开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class AddressHandler extends BaseTypeHandler <Address > { @Override public void setNonNullParameter (PreparedStatement ps, int i, Address parameter, JdbcType jdbcType) throws SQLException { if (parameter == null ) { return ; } StringBuilder builder = new StringBuilder(); String province = parameter.getProvince(); String city = parameter.getCity(); String street = parameter.getStreet(); builder.append(province) .append("," ) .append(city) .append("," ) .append(street); ps.setString(i, builder.toString()); } @Override public Address getNullableResult (ResultSet rs, String columnName) throws SQLException { String parameter = rs.getString(columnName); if (parameter == null || parameter.length() == 0 || !parameter.contains("," )) { return null ; } Address address = new Address(); address.setProvince(parameter.split("," )[0 ]); address.setCity(parameter.split("," )[1 ]); address.setStreet(parameter.split("," )[2 ]); return address; } @Override public Address getNullableResult (ResultSet rs, int columnIndex) throws SQLException { String parameter = rs.getString(columnIndex); if (parameter == null || parameter.length() == 0 || !parameter.contains("," )) { return null ; } Address address = new Address(); address.setProvince(parameter.split("," )[0 ]); address.setCity(parameter.split("," )[1 ]); address.setStreet(parameter.split("," )[2 ]); return address; } @Override public Address getNullableResult (CallableStatement cs, int columnIndex) throws SQLException { String parameter = cs.getString(columnIndex); if (parameter == null || parameter.length() == 0 || !parameter.contains("," )) { return null ; } Address address = new Address(); address.setProvince(parameter.split("," )[0 ]); address.setCity(parameter.split("," )[1 ]); address.setStreet(parameter.split("," )[2 ]); return address; } }
2.4.4 注册自定义类型处理器 2.4.4.1 方法一、字段级别:@ColumnType
注解 即在对应的实体类中的属性上加入@ColumnType(typeHandler=AddressTypeHandler.class)
注解进行标定。
这里是在User
中的Address
属性上加入此注解。
2.4.4.2 方法二、全局级别:在Mybatis配置文件中配置typeHandlers 1 2 3 <typeHandlers > <typeHandler handler ="cn.lizhi.Handler.AddressHandler" javaType ="cn.lizhi.domain.Address" /> </typeHandlers >
此时对Address
类复杂类型的注入进行测试,查询的返回结果:
1 2 3 4 5 6 7 8 9 @Test public void testQueryUser () { Integer userId = 1 ; User user = userService.findById(userId); System.out.println(user); }
2.4.5 枚举类型的转换 方法一:让通用Mapper把枚举类型作为简单类型处理
增加一个通用mapper的配置项,即在通用mapper的配置项中配置enumAsSimpleType=true
,其本质是使用了EnumTypeHandler
处理器。
方法二:为枚举类型配置对应的类型处理器
思路同Address转换为String,和String转化为Address思路相同。可以将枚举对象和String相互转换。
配置类型处理器
内置
org.apache.ibatis.type.EnumTypeHandler
:在数据库中配置的是枚举值本身
org.apache.ibatis.type.EnumOrdinalTypeHandler
:在数据库中存的是枚举类型的索引值(因为在枚举类型中,值是固定的)
自定义
内置处理器使用说明
不能使用@ColumnType
注解注册Mybatis原生注解;只能在Mybatis
全局配置文件中进行属性配置,并在属性上使用@Column
注解。如:
1 <typeHandler handler ="org.apache.ibatis.type.EnumTypeHandler" javaType ="cn.lizhi.domain.SeasonEnum" />
附
通用Mapper官方文档