什么是通用Mapper
通用Mapper都可以极大的方便开发人员。可以随意的按照自己的需要选择通用方法,还可以很方便的开发自己的通用方法。极其方便的使用MyBatis单表的增删改查。支持单表操作,不支持通用的多表联合查询。
添加POM文件
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapperartifactId>
<version>4.0.0-beta3version>
dependency>
创建测试数据
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');
Java实体类
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
private Integer empAge;
注意:考虑到基本数据类型在Java中都有默认值,会导致Mybatis在执行相关操作的时候很难判断当前字段是否为null,所以在Mybatis环境下Java实体类尽量不要使用基本数据类型,都使用对应的包装类型。
修改Sping的配置文件
<bean id="mapperScanner" class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
bean>
具体操作数据库的接口需要继承通用Mapper的核心接口
public interface EmployeeMapper extends Mapper<Employee> {
}
常用注解
SELECT emp_id,emp_name,emp_salary_apple,emp_age FROM tabple_emp WHERE emp_id = ?
AND emp_name = ? AND emp_salary_apple = ? AND emp_age = ?
之所以会生成上面这样的 WHERE 子句是因为通用 Mapper 将实体类中的所有
字段都拿来放在一起作为联合主键,这样就会导致查询失败。
情况 2:使用@Id 主键明确标记和数据库表中主键字段对应的实体类字段。
[外链图片转存失败(img-CKF2FOjO-1569398326056)(https://user-gold-cdn.xitu.io/2019/9/25/16d65e491d16e639?w=789&h=91&f=png&s=22948)]
//可以用于任意字符串类型长度超过32位的字段
@GeneratedValue(generator = "UUID")
private String username;
常用方法演示
Employee employee = new Employee(null, "bob", null, null);
Employee employee1 = employeeService.getOne(employee);
Employee employee = employeeService.getEmployeeMapper(3);
System.out.println(employee);
Employee employee = new Employee();
employee.setEmpAge(11);
employee.setEmpName("张三");
employee.setEmpSalary(100.00);
employeeService.insertEmployee(employee);
Integer empId = employee.getEmpId(); //这里可以返回插入这条数据的ID
System.out.println(empId);
Employee employee = new Employee(null, "张三", null, null);
employeeService.saveEmployeeSelective(employee);
Employee employee = new Employee(7, "dog", null, null);
employeeService.updateEmployeeSelective(employee);
Employee employee = null;
employeeService.removeEmployee(employee);
Integer empId = 7;
employeeService.deleteEmployeeByPrimaryKey(empId);
QBC查询
Query By Criteria。Criteria 是 Criterion 的复数形式。意思是:规则、标准、准则。在 SQL 语句中相当于查询条件。QBC 查询是将查询条件通过 Java 对象进行模块化封装。
示例
server
public List<Employee> getEmpListByExample(Example example) {
//QBC查询
return employeeMapper.selectByExample(example);
}
测试
@Test
public void testSelectByExample() {
Example example = new Example(Employee.class);
//i.设置排序信息 根据实体类中的字段
example.orderBy("empSalary").asc().orderBy("empAge").desc();//根据empSalary升序,根据empAge降序
//ii.设置“去重”
example.setDistinct(true);
//iii.设置select字段 表示查询出来的数据只列出指定的字段,其他的字段的数据不展示
example.selectProperties("empName","empSalary");
Example.Criteria criteria1 = example.createCriteria();
Example.Criteria criteria2 = example.createCriteria();
//它们之间是 或 的关系 or
criteria1.andGreaterThan("empSalary", 2000).//表示年龄小于25,工资高于2000
andLessThan("empAge", 25);
criteria2.andLessThan("empSalary", 5000).andGreaterThan("empAge", 30);
example.or(criteria2);
//传入example实例
List<Employee> employeeList = employeeService.getEmpListByExample(example);
for (Employee employee : employeeList) {
System.out.println(employee);
}
}
分页查询
//service
public List<Employee> getEmpListByRowBounds(RowBounds rowBounds) {
return employeeMapper.selectByRowBounds(null, rowBounds);
}
//test
@Test
public void testSelectByRowBounds() {
//此方法用来帮我们做分页的
//它并没有使用limit字句,它的实现原理就是把所有的数据都查询出来,放在内存中了,然后根据条件进行分页
//所以这个方法意义不大,真正做分页还是使用pageHelper
int pageNo = 3;
int pageSize = 5;
int index = (pageNo - 1) * pageSize;
RowBounds rowBounds = new RowBounds(index, pageSize);
List<Employee> empList = employeeService.getEmpListByRowBounds(rowBounds);
for (Employee employee : empList) {
System.out.println(employee);
}
}
用途
让我们可以根据开发的实际需要对 Mapper接口进行定制。
public interface MyMapper<T>
extends SelectAllMapper<T>,SelectByExampleMapper<T> {//可以自由选择
}
public interface EmployeeMapper extends MyMapper<Employee> {
}
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper.mappers"/>
<property name="properties">
<value>
mappers=com.example.mapper.mine_mappers.MyMapper
value>
property>
bean>
然后就可以测试使用了
这里的扩展指的是通用Mapper没有提供的功能
官方例子
举例
批量 update 作为扩展功能的例子:
类似于生成下面这样的SQL语句:
UPDATE tabple_emp SET emp_name=?,emp_age=?,emp_salary=? where emp_id=? ;
UPDATE tabple_emp SET emp_name=?,emp_age=?,emp_salary=? where emp_id=? ;
UPDATE tabple_emp SET emp_name=?,emp_age=?,emp_salary=? where emp_id=? ;
……
为了生成上面那样的 SQL 语句,我们需要使用到 MyBatis 的 foreach 标签。
<foreach collection="list" item="record" separator=";" >
UPDATE tabple_emp
SET emp_name=#{record.empName},
emp_age=#{record.empAge},
emp_salary=#{record.empSalary}
where emp_id=#{record.empId}
foreach>
@RegisterMapper //这个注解别忘了
public interface MyBatchUpdateMapper<T> {
//method是固定值dynamicSQL,type指定具体提供sql语句的类
@UpdateProvider(type=MyBatchUpdateProvider.class, method="dynamicSQL")
void batchUpdate(List<T> list);
}
public class MyBatchUpdateProvider extends MapperTemplate {
//首先继承MapperTemplate,然后重写构造方法
public MyBatchUpdateProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
}
/* 目的就是让这个方法为我们拼接一个字符串:下面 ↓
UPDATE tabple_emp
emp_name=#{record.empName},
emp_age=#{record.empAge},
emp_salary=#{record.empSalary},
where emp_id=#{record.empId}
*/
//然后提供一个同名的实现方法
public String batchUpdate(MappedStatement statement) {
//1.创建StringBuilder用于拼接SQL语句的各个组成部分
StringBuilder builder = new StringBuilder();
//2.拼接foreach标签
builder.append("" );
//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("" );
//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("");
//10.使用前面缓存的主键名、主键值拼接where子句
builder.append("where ").append(idColumn).append("=").append(idHolder);
builder.append("");
//11.将拼接好的字符串返回
return builder.toString();
}
}
public interface MyMapper<T>
extends SelectAllMapper<T>,SelectByExampleMapper<T>,
MyBatchUpdateMapper<T> {//继承自己定义的接口
}
public interface EmployeeMapper extends Mapper<Employee> {
}
service
@Service
public class EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
public void batchUpdateEmp(List<Employee> empList) {
employeeMapper.batchUpdate(empList);
}
}
这里MyMapper同样需要注册
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper.mappers"/>
<property name="properties">
<value>
mappers=com.example.mapper.mine_mappers.MyMapper
value>
property>
bean>
最后测试通过即可
ClassPathXmlApplicationContext iocContainer = new ClassPathXmlApplicationContext("spring-context.xml");
EmployeeService employeeService = iocContainer.getBean(EmployeeService.class);
List<Employee> empList = new ArrayList<>();
empList.add(new Employee(25, "newName01", 111.11, 10));
empList.add(new Employee(26, "newName02", 222.22, 20));
empList.add(new Employee(27, "newName03", 333.33, 30));
employeeService.batchUpdateEmp(empList);
iocContainer.close();
注意:实际开发中真正做缓存的都是redis、ehcache…这些
在mapper中加入此注解表示开启二级缓存,传入的实体类需要有序列化
简单类型和复杂类型
通用 Mapper 默认情况下会忽略复杂类型,对复杂类型不进行“从类到表”的映射。
这时候就需要我们自定义类型转换器了
public class AddressTypeHandler extends BaseTypeHandler<Address> {
/**
* 设置参数
* 将parameter对象转换成字符串存储到ps对象的i位置
* @param ps
* @param i
* @param address
* @param jdbcType
* @throws SQLException
*/
@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();
String street = address.getStreet();
/* 3.拼装成一个字符串
* 规则:各个值之间使用“,”分开 */
StringBuilder builder = new StringBuilder();
builder.append(province)
.append(",")
.append(city)
.append(",")
.append(street);
String parameterValue = builder.toString();
//4.设置参数
ps.setString(i, parameterValue);
}
/**
* 用列的名称取
* @param rs
* @param columnName
* @return
* @throws SQLException
*/
@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];
String street = split[2];
//5.根据具体对象组装一个Address对象
Address address = new Address(province, city, street);
return address;
}
//从结果集中获取数据库对应的查询结果
//将字符串还原为原始的T对象
/**
* 用列的索引取
* @param rs
* @param columnIndex
* @return
* @throws SQLException
*/
@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];
String street = split[2];
//5.根据具体对象组装一个Address对象
Address address = new Address(province, city, street);
return address;
}
/**
*
* @param cs
* @param columnIndex
* @return
* @throws SQLException
*/
@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];
String street = split[2];
//5.根据具体对象组装一个Address对象
Address address = new Address(province, city, street);
return address;
}
}
@ColumnType(typeHandler=AddressTypeHandler.class)//注册类型处理器
private Address address;
然后测试就OK了
@Column
private Address address;
然后在Mybatis配置文件中进行配置
<typeHandlers>
<typeHandler
handler="com.example.handlers.AddressTypeHandler"
javaType="com.example.entity.Address"/>
typeHandlers>
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mappers"/>
<property name="properties">
<value>
enumAsSimpleType=true
value>
property>
bean>
这里本质上是使用了 org.apache.ibatis.type.EnumTypeHandler<typeHandlers>
<typeHandler
handler="com.example.handlers.AddressTypeHandler"
javaType="com.example.entity.Address"/>
<typeHandler
handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.atguigu.mapper.entities.SeasonEnum"/>
typeHandlers>
※注意:加@Column 注解的作用是让通用 Mapper 不忽略枚举类型。