本文章的笔记整理来自尚硅谷视频https://www.bilibili.com/video/BV1mW411M737
(1)MyBatis历史
原是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation 迁移到了Google Code,随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis ,代码于2013年11月迁移到Github。iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。
(2)MyBatis特点
① MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的半自动化持久层框架。
② MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
③ MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。
(3)MyBatis与其它对应框架或组件的对比
MyBatis | 半自动化的持久化层框架 |
---|---|
JDBC | ① SQL夹在Java代码块里,耦合度高导致硬编码内伤。② 维护不易且实际开发需求中sql是有变化,频繁修改的情况多见。 |
Hibernate和JPA | ① 长难复杂SQL,对于Hibernate而言处理也不容易内部自动生产的SQL,不容易做特殊优化。② 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难,这会导致数据库性能下降。 |
总之,对开发人员而言,核心sql还是需要自己来优化的。除此之外,如果sql代码和java代码分开,那么它们的功能边界就更加清晰,一个专注业务、一个专注数据。
(4)MyBatis地址为https://github.com/mybatis/mybatis-3/
(1)创建测试表tbl_employee并添加一条测试数据,表结构及数据如下:
(2)在IDEA中创建一个Java项目,并在项目的根目录下创建lib目录(用于存放要导入的 jar 包)和config目录(用于存放配置文件),另外,需要右键点击config目录,找到Mark Directoy as并选择Resources Root。
各个部分的具体代码如下:
① 日志配置文件log4j.xml
DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
layout>
appender>
<logger name="java.sql">
<level value="debug" />
logger>
<logger name="org.apache.ibatis">
<level value="info" />
logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
root>
log4j:configuration>
② 与数据库中的表tbl_employee对应的实体类Employee.java
package com.atguigu.mybatis.bean;
import org.apache.ibatis.type.Alias;
//@Alias("别名"):单独为Employee起别名
//@Alias("emp")
public class Employee {
//属性名称应该与对应数据库表(tbl_employee)中的字段一致
private Integer id;
private String last_name;
private String email;
private String gender;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLast_name() {
return last_name;
}
public void setLast_name(String last_name) {
this.last_name = last_name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + last_name + ", email="
+ email + ", gender=" + gender + "]";
}
}
③ Mapper接口EmployeeMapper.java
package com.atguigu.mybatis.dao;
import com.atguigu.mybatis.bean.Employee;
public interface EmployeeMapper {
public Employee getEmpById(Integer id);
}
④ sql映射文件EmployeeMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id = #{id}
select>
mapper>
⑤ 数据库配置文件dbconfig.properties
jdbc.drive=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
⑥ mybatis的全局配置文件mybatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="dbconfig.properties">properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.drive}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
dataSource>
environment>
environments>
<mappers>
<mapper resource="EmployeeMapper.xml" />
mappers>
configuration>
⑦ 测试代码
package com.atguigu.mybatis.test;
import com.atguigu.mybatis.bean.Employee;
import com.atguigu.mybatis.dao.EmployeeMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class MyBatisTest {
//根据全局配置文件mybatis-config.xml,利用SqlSessionFactoryBuilder创建SqlSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
/*
* 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象,其包含数据源等一些运行环境信息
* 2、sql映射文件;配置了每一个sql,以及sql的封装规则等。
* 3、将sql映射文件注册在全局配置文件中
* 4、写代码:
* 1)、根据全局配置文件得到SqlSessionFactory;
* 2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
* 一个sqlSession就是代表和数据库的一次会话,用完关闭
* 3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
*
* @throws IOException
*/
//旧版本常用方式
@Test
public void test01() throws IOException {
// 获取sqlSession实例,能直接执行已经映射的sql语句
// sql的唯一标识:statement Unique identifier matching the statement to use.
// 执行sql要用的参数:parameter A parameter object to pass to the statement.
//使用SqlSessionFactory获取sqlSession对象,一个SqlSession对象代表和数据库的一次会话
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try {
Employee employee = openSession.selectOne(
"com.atguigu.mybatis.dao.EmployeeMapper.getEmpById", 1);
System.out.println(employee);
} finally {
openSession.close();
}
}
//新版本常用的方式:接口式编程
/*
* 1、接口式编程
* 原生: Dao ====> DaoImpl
* mybatis: Mapper ====> xxMapper.xml
*
* 2、SqlSession代表和数据库的一次会话;用完必须关闭;
* 3、SqlSession和connection一样她都是非线程安全。每次使用都应该去获取新的对象。
* 4、mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。
* (将接口和xml进行绑定)
* EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);
* 5、两个重要的配置文件:
* mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等...系统运行环境信息
* sql映射文件:保存了每一个sql语句的映射信息:将sql抽取出来。
*
*/
@Test
public void test02() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(1);
System.out.println(mapper.getClass());
System.out.println(employee);
}finally{
openSession.close();
}
}
}
MyBatis可以使用properties来引入外部properties配置文件的内容。
# 数据库配置文件dbconfig.properties
jdbc.drive=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
<properties resource="dbconfig.properties">properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.drive}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
dataSource>
environment>
environments>
settings设置是MyBatis 中极为重要的调整设置,它们会改变MyBatis 运行时的行为。
例如开启自动驼峰命名规则映射:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
typeAliases:别名处理器,它可以为java类型起别名(别名不区分大小写)
<typeAliases>
<package name="com.atguigu.mybatis.bean"/>
typeAliases>
除此之外,MyBatis已经为Java中一些常见的数据类型起好了别名,在开发时直接使用即可。
无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。后面会有专门的章节来介绍mybatis运行原理以及插件。
• Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
• ParameterHandler (getParameterObject, setParameters)
• ResultSetHandler (handleResultSets, handleOutputParameters)
• StatementHandler (prepare, parameterize, batch, update, query)
MyBatis可以配置多种环境,例如开发、测试和生产环境需要有不同的配置。每种环境使用一个environment标签进行配置并指定唯一标识符。可以通过environments标签中的default属性指定一个环境的标识符来快速地切换环境。
① environment——指定具体的环境:
id | 指定当前环境的唯一标识 |
---|---|
transactionManager、dataSource | 分别表示事务管理、数据源,必须存在于environment中 |
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.drive}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
dataSource>
environment>
environments>
② transactionManager——事务管理
type | JDBC、MANAGED、自定义 |
---|---|
JDBC | 使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范围 |
MANAGED | 不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文) |
自定义 | 实现TransactionFactory接口,type=全类名/别名 |
③ dataSource——数据源
type | UNPOOLED、POOLED、JNDI、自定义 |
---|---|
UNPOOLED | 不使用连接池 |
POOLED | 使用连接池 |
JNDI | 在EJB 或应用服务器这类容器中查找指定的数据源 |
自定义 | 实现DataSourceFactory接口,定义数据源的获取方式 |
注:在实际开发中,一般使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置。 |
MyBatis 可以根据不同的数据库厂商执行不同的语句。
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<property name="SQL Server" value="sqlserver"/>
databaseIdProvider>
在sql映射文件中编写具体的sql语句时指定需要操作的数据库(当然这需要提前导入相应的jar包以及配置好数据库环境、数据源信息等,这样才能顺利切换需要操作的数据库)。
<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee" databaseId="mysql">
select * from tbl_employee where id = #{id}
select>
MyBatis匹配规则如下:
① 如果没有配置databaseIdProvider标签,那么databaseId=null;
② 如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为null;
③ 如果databaseId不为null,他只会找到配置databaseId的sql语句;
④ MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带databaseId 的相同语句,则后者会被舍弃;
mappers将写好的sql映射文件注册到全局配置中。
<mappers>
<mapper resource="EmployeeMapper.xml" />
<mapper class="com.atguigu.mybatis.dao.EmployeeMapperAnnotation"/>
<package name="com.atguigu.mybatis.dao"/>
mappers>
注:当使用mapper中的class属性来注册接口时,不需要映射文件并且sql语句利用注解写在接口上的例子如下:
package com.atguigu.mybatis.dao;
import com.atguigu.mybatis.bean.Employee;
import org.apache.ibatis.annotations.Select;
public interface EmployeeMapperAnnotation {
//直接在注解中编写sql语句,不要sql映射文件
@Select("select * from tbl_employee where id = #{id}")
public Employee getEmpById(Integer id);
}
以上介绍了MyBatis中常用的标签,所有的标签如下图所示。不过在使用这些标签时也需要按照下图中从上到下规定的顺序(某个标签可以没有,但如果有则顺序不能颠倒,否则会报错!!!)
映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义。
① 在接口EmployeeMapper中编写抽象的增删改查方法
package com.atguigu.mybatis.dao;
import com.atguigu.mybatis.bean.Employee;
public interface EmployeeMapper {
Employee getEmpById(Integer id);
void addEmp(Employee employee);
boolean updateEmp(Employee employee);
void deleteEmpById(Integer id);
}
② 在对应的sql映射文件EmployeeMapper.xml中编写sql语句
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee" databaseId="mysql">
select * from tbl_employee where id = #{id}
select>
<insert id="addEmp" parameterType="com.atguigu.mybatis.bean.Employee">
insert into tbl_employee(last_name,email,gender)
values (#{last_name},#{email},#{gender})
insert>
<update id="updateEmp">
update tbl_employee
set last_name=#{last_name},email=#{email},gender=#{gender}
where id=#{id}
update>
<delete id="deleteEmpById">
delete from tbl_employee where id=#{id}
delete>
mapper>
③ 测试
/*
* 测试增删改
* 1、mybatis允许增删改直接定义以下类型返回值
* Integer、Long、Boolean、void
* 2、我们需要手动提交数据
* sqlSessionFactory.openSession();===》手动提交
* sqlSessionFactory.openSession(true);===》自动提交
*/
@Test
public void test04() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
//添加数据
Employee employee1 = new Employee(null, "Jerry", "1","[email protected]");
mapper.addEmp(employee1);
//修改数据
Employee employee2 = new Employee(2, "Hi", "1","[email protected]");
//若修改影响的行数大于等于1,则返回true,否则返回false
boolean flag = mapper.updateEmp(employee2);
System.out.println(flag);
//删除数据
mapper.deleteEmpById(2);
//手动提交数据
openSession.commit();
}finally{
openSession.close();
}
}
除此之外,insert、update、delete标签中常见的属性如下:
<insert id="addEmp" parameterType="com.atguigu.mybatis.bean.Employee" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,email,gender)
values (#{last_name},#{email},#{gender})
insert>
//添加数据
Employee employee = new Employee(null, "Jerry", "1","[email protected]");
mapper.addEmp(employee);
//输出得到的自增主键的值
System.out.println(employee.getId());
<insert id="addEmp" databaseId="oracle">
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
select EMPLOYEES_SEQ.nextval from dual
selectKey>
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
values(#{id},#{lastName},#{email})
insert>
对于单个参数,mybatis不会做特殊处理,即按照 #{参数名/任意名} 就可以取出参数值。
<delete id="deleteEmpById">
delete from tbl_employee where id=#{id}
delete>
对于多个参数,mybatis会做特殊处理,即多个参数会被封装成一个map,其中key为param1…paramN(或者参数的索引),value为传入的参数值,#{x}就等于从map中获取 key 为 x 的value的值。
① 定义含有2个参数的抽象方法:
Employee getEmpByIdAndLast_name(Integer id,String last_name);
② 错误示例:按照单个参数的方式进行取值
<select id="getEmpByIdAndLast_name" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id=#{id} and last_name=#{last_name}
select>
@Test
public void test05() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpByIdAndLast_name(1, "Tom");
System.out.println(employee);
openSession.commit();
}finally{
openSession.close();
}
}
错误提示如下:
### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2]
### Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2]
③ 正确示例
<select id="getEmpByIdAndLast_name" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id=#{param1} and last_name=#{param2}
select>
对于多个参数的处理还有另外一种方法,即命名参数,使用注解@Param明确指定封装参数时map的key。
Employee getEmpByIdAndLast_name(@Param("id") Integer id,@Param("last_name") String last_name);
<select id="getEmpByIdAndLast_name" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id=#{id} and last_name=#{last_name}
select>
① 如果多个参数正好是我们业务逻辑的数据模型,那么就可以直接传入对应的POJO(Plain Old Java Objects,普通的Java对象),然后按照 #{属性名} 的方式取出传入的POJO的属性值。
boolean updateEmp(Employee employee);
<update id="updateEmp">
update tbl_employee
set last_name=#{last_name},email=#{email},gender=#{gender}
where id=#{id}
update>
② 如果多个参数不是业务模型中的数据,没有对应的pojo,为了方便,可以传入map。
Employee getEmpByMap(Map<String,Object> map);
<select id="getEmpByMap" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id=#{id} and last_name=#{last_name}
select>
@Test
public void test05() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("id",1);
map.put("last_name","Tom");
Employee employee = mapper.getEmpByMap(map);
System.out.println(employee);
openSession.commit();
}finally{
openSession.close();
}
}
③ 如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)来数据传输对象。
例如对于数据的分页处理,我们可以单独定义一个Page类,里面自定义需要的属性,其用法与POJO类似。
public Employee getEmp(@Param(“id”)Integer id,String last_name); | 取值:id==>#{id/param1} last_name==>#{param2} |
---|---|
public Employee getEmp(Integer id, @Param(“e”)Employee emp); | 取值:id==>#{param1} last_name===>#{param2.last_name/e.last_name} |
如果是Collection(List、Set)类型或者是数组,也会特殊处理,也是把传入的list或者数组封装在map中 | key:Collection(collection) |
如果是List数组 | 使用这个key(list)数组(array) |
public Employee getEmpById(List ids); | 取值:取出第一个id的值: #{list[0]} |
① #{ } 与 ${ }都可以获取map中的值或者pojo对象属性的值。
② 在具体的细节上#{ } 与 ${ }有一些不同:
#{ } | 是以预编译的形式,将参数设置到 sql 语句中,可以防止 sql 注入 |
---|---|
${ } | 取出的值直接拼装在 sql 语句中,可能会存在安全问题 |
③ 在大多情况下,我们应该使用 #{ } 去取参数的值,但是在一些特殊情况下需要使用 ${ }:
# 在原生JDBC不支持占位符的地方,就可以使用${}进行取值,例如分表、排序等
select * from ${year}_salary where xxx;
select * from ${tbl_name} where id=${id} and last_name=#{last_name};
Select标签用来定义查询操作,常用的属性如下:
id | 唯一标识符,用来引用这条语句,需要和接口的方法名一致 |
---|---|
parameterType | 参数类型,可以不传,MyBatis会根据TypeHandler自动推断 |
resultType | 返回值类型,别名或者全类名,如果返回的是集合,定义集合中元素的类型,不能和resultMap同时使用 |
其余属性如下: | |
List<Employee> getEmpsByLast_NameLike(String last_name);
<select id="getEmpsByLast_NameLike" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where last_name like #{last_name}
select>
@Test
public void test06() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
//查询姓名中含有字母e的所有员工
List<Employee> emps = mapper.getEmpsByLast_NameLike("%e%");
System.out.println(emps);
openSession.commit();
}finally{
openSession.close();
}
}
1)返回一条记录的map,key为列名,value为对应的值
Map<String,Object> getEmpByIdReturnMap(Integer id);
<select id="getEmpByIdReturnMap" resultType="map">
select * from tbl_employee where id=#{id}
select>
@Test
public void test06() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Map<String, Object> map = mapper.getEmpByIdReturnMap(1);
System.out.println(map);
openSession.commit();
}finally{
openSession.close();
}
}
2)多条记录封装一个map:Map
//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey("id")
Map<String, Employee> getEmpByLast_NameLikeReturnMap(String last_name);
<select id="getEmpByLast_NameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where last_name like #{last_name}
select>
@Test
public void test06() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Map<String, Employee> map = mapper.getEmpByLast_NameLikeReturnMap("%r%");
System.out.println(map);
openSession.commit();
}finally{
openSession.close();
}
}
package com.atguigu.mybatis.dao;
import com.atguigu.mybatis.bean.Employee;
public interface EmployeeMapperPlus{
Employee getEmpById(Integer id);
}
注:记得要在MyBatis全局配置文件(mybatis-config.xml)中注册新的sql映射文件!
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapperPlus">
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmp">
<id column="id" property="id"/>
<result column="last_name" property="last_name"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
resultMap>
<select id="getEmpById" resultMap="MyEmp">
select * from tbl_employee where id=#{id}
select>
mapper>
1)创建一张新的表 tbl_dept
create table tbl_dept(
id INT(11) primary key AUTO_INCREMENT,
departmentName VARCHAR(255)
)
2)为表 tbl_employee 加上一列,用来存储员工所在的部门id
alter table tbl_employee add COLUMN d_id INT(11);
3)将表 tbl_employee 中的字段d_id(员工所在的部门id)设置为外键
ALTER TABLE tbl_employee add CONSTRAINT fk_emp_dept FOREIGN key(d_id) REFERENCES tbl_dept(id);
4)创建部门类Department
package com.atguigu.mybatis.bean;
public class Department {
private Integer id;
private String departmentName;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
@Override
public String toString() {
return "Department{" +
"id=" + id +
", departmentName='" + departmentName + '\'' +
'}';
}
}
5)在Employee.java中添加新的属性(即员工所在的部门Department),并添加对应的 get/set 方法
private Department dept;
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
//EmployeeMapperPlus.java
Employee getEmpAndDept(Integer id);
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp">
<id column="id" property="id"/>
<result column="last_name" property="last_name"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<result column="did" property="dept.id"/>
<result column="deptName" property="dept.deptName"/>
resultMap>
<select id="getEmpAndDept" resultMap="MyDifEmp">
SELECT e.id id,e.last_name last_name,e.gender gender,e.email email,e.d_id d_id,
d.id did,d.deptName deptName
FROM tbl_employee e,tbl_dept d
WHERE e.d_id=d.id AND e.id=#{id}
select>
@Test
public void test06() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapperPlus mapper = openSession.getMapper(EmployeeMapperPlus.class);
Employee employee = mapper.getEmpAndDept(1);
System.out.println(employee);
System.out.println(employee.getDept());
openSession.commit();
}finally{
openSession.close();
}
}
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp2">
<id column="id" property="id"/>
<result column="last_name" property="last_name"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<association property="dept" javaType="com.atguigu.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="deptName" property="deptName"/>
association>
resultMap>
1)新建DepartmentMapper.java,并定义抽象方法getDeptById()。
package com.atguigu.mybatis.dao;
import com.atguigu.mybatis.bean.Department;
public interface DepartmentMapper {
//根据部门id查询部门信息
Department getDeptById(Integer id);
}
2)新建对应的sql映射文件(记得要在MyBatis全局配置文件(mybatis-config.xml)中注册)。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.DepartmentMapper">
<select id="getDeptById" resultType="com.atguigu.mybatis.bean.Department">
select id,deptName from tbl_dept where id=#{id}
select>
mapper>
3)在EmployeeMapperPlus.java中增加抽象方法getEmpByIdStep()。
//EmployeeMapperPlus.java
Employee getEmpByIdStep(Integer id);
4)在EmployeeMapperPlus.xml中编写对应的sql语句。
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpByStep">
<id column="id" property="id"/>
<result column="last_name" property="last_name"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
association>
resultMap>
<select id="getEmpByIdStep" resultMap="MyEmpByStep">
select * from tbl_employee where id=#{id}
select>
5)测试
@Test
public void test06() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapperPlus mapper = openSession.getMapper(EmployeeMapperPlus.class);
//分步查询
Employee employee = mapper.getEmpByIdStep(1);
System.out.println(employee);
System.out.println(employee.getDept());
openSession.commit();
}finally{
openSession.close();
}
}
6)延迟加载——在MyBatis全局配置文件(mybatis-config.xml)中加上两个设置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
1)在Department.java中添加属于该部门的所有员工的集合List,并添加相应的 get/set 方法。
package com.atguigu.mybatis.bean;
import java.util.List;
public class Department {
private Integer id;
private String deptName;
private List<Employee> emps;
public List<Employee> getEmps() {
return emps;
}
public void setEmps(List<Employee> emps) {
this.emps = emps;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
@Override
public String toString() {
return "Department{" +
"id=" + id +
", deptName='" + deptName + '\'' +
'}';
}
}
2)在EmployeeMapperPlus.java中添加抽象方法getDeptByIdPlus()。
//EmployeeMapperPlus.java
//通过部门id查询部门信息,以及属于该部门的所有员工的集合List
Department getDeptByIdPlus(Integer id);
3)在DepartmentMapper.xml中编写上述方法对应的sql语句。
<resultMap id="MyDept" type="com.atguigu.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="deptName" property="deptName"/>
<collection property="emps" ofType="com.atguigu.mybatis.bean.Employee">
<id column="eid" property="id"/>
<result column="last_name" property="last_name"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
collection>
resultMap>
<select id="getDeptByIdPlus" resultMap="MyDept">
select d.id did,d.deptName deptName,
e.id eid,e.last_name last_name,e.email email,e.gender gender
from tbl_dept d
left join tbl_employee e
on d.id=e.d_id
where d.id=#{id};
select>
4)测试
@Test
public void test07() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
DepartmentMapper mapper = openSession.getMapper(DepartmentMapper.class);
//查询部门id为1的部门信息,以及属于该部门的所有员工信息(在list中)
Department dept = mapper.getDeptByIdPlus(1);
//输出部门信息
System.out.println(dept);
//输出属于该部门的所有员工信息
System.out.println(dept.getEmps());
openSession.commit();
}finally{
openSession.close();
}
}
1)在EmployeeMapperPlus.java中添加抽象方法getEmpsByDeptId()。
//EmployeeMapperPlus.java
//根据部门id查询所有属于该部门的员工信息
List<Employee> getEmpsByDeptId(Integer deptId);
2)在EmployeeMapperPlus.xml中编写对应的sql语句。
<select id="getEmpsByDeptId" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where d_id=#{deptId};
select>
3)在DepartmentMapper.java中添加抽象的分步查询方法getDeptByIdStep()。
//DepartmentMapper.java
//通过部门id查询部门信息,以及属于该部门的所有员工的集合List(分步查询)
Department getDeptByIdStep(Integer id);
4)在DepartmentMapper.xml中编写对应的sql语句。
<resultMap type="com.atguigu.mybatis.bean.Department" id="MyDeptStep">
<id column="id" property="id"/>
<id column="deptName" property="deptName"/>
<collection property="emps"
select="com.atguigu.mybatis.dao.EmployeeMapperPlus.getEmpsByDeptId"
column="id">collection>
resultMap>
<select id="getDeptByIdStep" resultMap="MyDeptStep">
select id,deptName from tbl_dept where id=#{id}
select>
5)扩展——传递多列值
在collection分步查询时,其中column表示需要传递的参数,当只有一个参数时,直接写进去即可:
当需要传递多个参数时,选需要将多列的值封装在map中进行传递,column=“{key1=column1,key2=column2}”:
6)扩展——延迟加载
fetchType=“lazy”:表示使用延迟加载,fetchType=“eager”:表示使用立即加载。(该设置可以覆盖掉全局配置文件中的延迟加载)
测试代码:
@Test
public void test07() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
DepartmentMapper mapper = openSession.getMapper(DepartmentMapper.class);
Department dept = mapper.getDeptByIdStep(1);
System.out.println(dept.getDeptName());
System.out.println(dept.getEmps());
openSession.commit();
}finally{
openSession.close();
}
}
在EmployeeMapper.xml中添加鉴别器
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpDis">
<id column="id" property="id"/>
<result column="last_name" property="last_name"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<discriminator javaType="string" column="gender">
<case value="0" resultType="com.atguigu.mybatis.bean.Employee">
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
association>
case>
<case value="1" resultType="com.atguigu.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="last_name"/>
<result column="last_name" property="email"/>
<result column="gender" property="gender"/>
case>
discriminator>
resultMap>
① 动态 SQL是MyBatis强大特性之一,它极大地简化拼装SQL的操作,动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。此外,MyBatis 采用了功能强大的基于 OGNL 的表达式来简化操作。
② 环境搭建
新建EmployeeMapperDynamicSQL.java
package com.atguigu.mybatis.dao;
public interface EmployeeMapperDynamicSQL {
}
新建EmployeeMapperDynamicSQL.xml(记得要在全局配置文件中注册该sql映射文件!)
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapperDynamicSQL">
mapper>
① 在EmployeeMapperDynamicSQL.java中添加抽象方法
//携带了哪个字段查询条件就带上这个字段的值
List<Employee> getEmpsByConditionIf(Employee employee);
② 在EmployeeMapperDynamicSQL.xml中编写对应的sql语句
<select id="getEmpsByConditionIf" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
where 1=1
<if test="id!=null">
and id=#{id}
if>
<if test="last_name!=null and last_name!=''">
and last_name like #{last_name}
if>
<if test="email!=null and email.trim()!=''">
and email=#{email}
if>
<if test="gender==0 or gender==1">
and gender=#{gender}
if>
select>
③ 测试
@Test
public void testDynamicSQL() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
Employee employee = new Employee(null,"%e%",null,null);
List<Employee> emps = mapper.getEmpsByConditionIf(employee);
System.out.println(emps);
openSession.commit();
}finally{
openSession.close();
}
}
查询的时候如果某些条件没带可能会出现sql拼装问题,现有以下两种解决方案:
① 给where后面加上1=1,以后的条件都是and xxx(上面(2)中的代码就是这样写的)。
② MyBatis使用 where 标签来将所有的查询条件包括在内。即在where标签拼装sql语句,并且会去掉多出来的and或者or,但是where只会去掉第一个多出来的and或者or。
<select id="getEmpsByConditionIf" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
<where>
<if test="id!=null">
id=#{id}
if>
<if test="last_name!=null and last_name!=''">
and last_name like #{last_name}
if>
<if test="email!=null and email.trim()!=''">
and email=#{email}
if>
<if test="gender==0 or gender==1">
and gender=#{gender}
if>
where>
select>
① 在EmployeeMapperDynamicSQL.java中添加抽象方法
List<Employee> getEmpsByConditionTrim(Employee employee);
② 在EmployeeMapperDynamicSQL.xml中编写对应的sql语句
<select id="getEmpsByConditionTrim" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
if>
<if test="last_name!=null and last_name!=''">
last_name like #{last_name} and
if>
<if test="email!=null and email.trim()!=''">
email=#{email} and
if>
<if test="gender==0 or gender==1">
gender=#{gender}
if>
trim>
select>
① 在EmployeeMapperDynamicSQL.java中添加抽象方法
//如果带了id就用id查,如果带了last_name就用last_name查,只会进入其中一个
List<Employee> getEmpsByConditionChoose(Employee employee);
② 在EmployeeMapperDynamicSQL.xml中编写对应的sql语句
<select id="getEmpsByConditionChoose" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
<where>
<choose>
<when test="id!=null">
id=#{id}
when>
<when test="last_name!=null">
last_name like #{last_name}
when>
<when test="email!=null">
email = #{email}
when>
<otherwise>
gender = 0
otherwise>
choose>
where>
select>
① 在EmployeeMapperDynamicSQL.java中添加抽象方法
//更新employee中不为空的属性(列),即哪列有值就更新哪列(id主键必须有值)
void updateEmp(Employee employee);
② 在EmployeeMapperDynamicSQL.xml中编写对应的sql语句
<update id="updateEmp">
update tbl_employee
<set>
<if test="last_name!=null">
last_name=#{last_name},
if>
<if test="email!=null">
email=#{email},
if>
<if test="gender!=null">
gender=#{gender}
if>
set>
where id=#{id}
update>
① 在EmployeeMapperDynamicSQL.java中添加抽象方法
//查询所有id在给定集合中的员工
List<Employee> getEmpsByConditionForeach(@Param("ids")List<Integer> ids);
② 在EmployeeMapperDynamicSQL.xml中编写对应的sql语句
<select id="getEmpsByConditionForeach" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
<foreach collection="ids" item="item_id" separator=","
open="where id in(" close=")">
#{item_id}
foreach>
select>
③ 测试
@Test
public void testDynamicSQL() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
Employee employee = new Employee(null,null,null,null);
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
List<Employee> emps = mapper.getEmpsByConditionForeach(ids);
for (Employee emp : emps) {
System.out.println(emp);
}
openSession.commit();
}finally{
openSession.close();
}
}
① 在EmployeeMapperDynamicSQL.java中添加抽象方法
//批量插入员工信息
void addEmps(@Param("emps")List<Employee> emps);
② 在EmployeeMapperDynamicSQL.xml中编写对应的sql语句
<!--
批量保存
-->
<insert id="addEmps">
insert into tbl_employee(last_name, email, gender,d_id)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.last_name},#{emp.email},#{emp.gender},#{emp.dept.id})
foreach>
insert>
<insert id="addEmps">
<foreach collection="emps" item="emp" separator=";">
insert into tbl_employee(last_name,email,gender,d_id)
values(#{emp.last_name},#{emp.email},#{emp.gender},#{emp.dept.id})
foreach>
insert>
③ 测试
@Test
public void testBatchSave() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
List<Employee> emps = new ArrayList<>();
emps.add(new Employee(null,"Smith","1","[email protected]",new Department(1)));
emps.add(new Employee(null,"Smith2","1","[email protected]",new Department(2)));
9 mapper.addEmps(emps);
openSession.commit();
}finally{
openSession.close();
}
}
① 在EmployeeMapperDynamicSQL.java中添加抽象方法
List<Employee> getEmpsTestInnerParameter(Employee employee);
② 在EmployeeMapperDynamicSQL.xml中编写对应的sql语句
<select id="getEmpsTestInnerParameter" resultType="com.atguigu.mybatis.bean.Employee">
<if test="_databaseId=='mysql'">
select * from tbl_employee
<if test="_parameter!=null">
where last_name like #{_parameter.last_name}
if>
if>
<if test="_databaseId=='oracle'">
select * from tbl_employee
if>
select>
bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值。以上面的sql语句为例:
<select id="getEmpsTestInnerParameter" resultType="com.atguigu.mybatis.bean.Employee">
<bind name="_lastName" value="'%'+last_name+'%'"/>
<if test="_databaseId=='mysql'">
select * from tbl_employee
<if test="_parameter!=null">
where last_name like #{_lastName}
if>
if>
<if test="_databaseId=='oracle'">
select * from tbl_employee
if>
select>
测试代码如下:
@Test
public void testBind() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
Employee employee = new Employee();
employee.setLast_name("e");
List<Employee> emps = mapper.getEmpsTestInnerParameter(employee);
System.out.println(emps);
openSession.commit();
}finally{
openSession.close();
}
}
抽取可重用的sql片段,方便后面引用
① sql抽取:将经常要查询的列名,或者插入用的列名抽取出来,方便引用;
② 用include来引用已经抽取的sql;
③ 此外,include还可以自定义一些property,sql标签内部就能使用自定义的属性;include-property:正确的取值方式为${prop}
<sql id="insertColumn">
<if test="_databaseId=='oracle'">
employee_id,last_name,email
if>
<if test="_databaseId=='mysql'">
last_name,email,gender,d_id
if>
sql>
<insert id="addEmps">
insert into tbl_employee(<include refid="insertColumn">include>)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.last_name},#{emp.email},#{emp.gender},#{emp.dept.id})
foreach>
insert>
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存极大地提升了查询效率。MyBatis中默认定义了两级缓存,即一级缓存和二级缓存。
一级缓存(local cache),即本地缓存,作用域默认为sqlSession。当 Session flush 或 close 后,该Session 中的所有 Cache 将被清空。除此之外,本地缓存不能被关闭,但是可以调用 clearCache() 来清空本地缓存,或者改变缓存的作用域。
二级缓存(second level cache),即全局作用域缓存,它是基于namespace级别的缓存,一个namespace对应一个二级缓存。二级缓存在默认情况下不开启,若要开启则需要手动配置。MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口。
二级缓存的工作机制如下:
1)一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
2)如果会话关闭,一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;
3)不同namespace查出的数据会放在自己对应的缓存中(map)
4)查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中,那么此时的效果就是数据会从二级缓存中获取。
同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中。
@Test
public void testFirstLevelCache() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
//xxx
//再次调用getEmpById()方法
Employee emp02 = mapper.getEmpById(1);
System.out.println(emp02);
//判断emp01和emp02是否为同一个对象
System.out.println(emp01==emp02);
openSession.commit();
}finally{
openSession.close();
}
}
① 不同的SqlSession对应不同的一级缓存
② 同一个SqlSession但是查询条件不同
③ 两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
④ 同一个SqlSession两次查询期间手动清空了缓存(openSession.clearCache())
① 在全局配置文件 mybatis-config.xml 中开启全局二级缓存配置:
<setting name="cacheEnabled" value="true"/>
② 去相应的mapper.xml(以EmployeeMapper.xml为例)中配置使用二级缓存:
<cache eviction="FIFO" flushInterval="" readOnly="false" size="1024">cache>
③ 相应的POJO(Employee.java、Department.java)需要实现序列化接口
④ 测试
@Test
public void testSecondLevelCache() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
SqlSession openSession2 = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
//会话关闭,那么一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容
openSession.close();
//第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
Employee emp02 = mapper2.getEmpById(1);
System.out.println(emp02);
System.out.println(emp01==emp02);
openSession.close();
}finally{
}
}
全局setting的cacheEnable | 配置二级缓存的开关,true为打开,false为关闭(一级缓存一直是打开的) |
---|---|
全局setting的localCacheScope | 值为SESSION时,当前会话的所有数据保存在会话缓存中;值为STATEMENT时,禁用一级缓存 |
select标签的useCache属性 | 配置这个select是否使用二级缓存,true为使用,false为不使用(一级缓存一直是使用的 ) |
sql标签的flushCache属性 | 在增删改标签中默认flushCache=true,即sql执行以后,会同时清空一级和二级缓存。在查询标签中则默认flushCache=false |
sqlSession.clearCache() | 只用来清除当前sqlSession一级缓存 |
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="E:\ehcache" />
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
defaultCache>
ehcache>
<cache type="org.mybatis.caches.ehcache.EhcacheCache">cache>
@Test
public void testSecondLevelCache() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
SqlSession openSession2 = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
openSession.close();
//第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
Employee emp02 = mapper2.getEmpById(1);
System.out.println(emp02);
System.out.println("emp01==emp02的结果为:"+(emp01==emp02));
openSession.close();
}finally{
}
}
在DepartmentMapper.xml中引用缓存
<cache-ref namespace="com.atguigu.mybatis.dao.EmployeeMapper"/>
此处的SSM框架整合未使用Maven等项目管理工具,若想看Maven版本的可以查看这篇文章。
右键lib,点击"Add as Library…",再点击OK即可。
整体的项目结构如下:
有需要的可以下载本项目的源代码(提取码:test)
MyBatis Generator简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件、接口、以及bean类等。它支持基本的增删改查,以及QBC风格的条件查询。但是表连接、存储过程等这些复杂sql的定义需要我们手工编写。
① 将之前Mybatis项目复制一份并改名为MyBatis_07_mbg,删除一些不用的文件,并导入Myabtis Generator的包:
② 新建mbg.xml文件,放在src目录下(可以先从官网中复制过来,然后再根据实际情况进行修改)
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true"
userId="root"
password="123456">
jdbcConnection>
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
javaTypeResolver>
<javaModelGenerator targetPackage="com.atguigu.mybatis.bean"
targetProject=".\src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
javaModelGenerator>
<sqlMapGenerator targetPackage="com.atguigu.mybatis.dao"
targetProject=".\conf">
<property name="enableSubPackages" value="true" />
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.dao"
targetProject=".\src">
<property name="enableSubPackages" value="true" />
javaClientGenerator>
<table tableName="tbl_dept" domainObjectName="Department">table>
<table tableName="tbl_employee" domainObjectName="Employee">table>
context>
generatorConfiguration>
③ 使用MyBatis逆向工程生成相应的代码及配置,即运行下面官网提供的代码:
@Test
public void testMbg() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("mbg.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
④ 测试
@Test
public void testMyBatis3() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
//xxxExample就是封装查询条件的
//1、查询所有
//List emps = mapper.selectByExample(null);
//2、查询员工名字中有e字母的,和员工性别是1的
//封装员工查询条件的example
EmployeeExample example = new EmployeeExample();
//创建一个Criteria,这个Criteria就是拼装查询条件
//select id, last_name, email, gender, d_id from tbl_employee
//WHERE ( last_name like ? and gender = ? ) or email like "%e%"
Criteria criteria = example.createCriteria();
criteria.andLastNameLike("%e%");
criteria.andGenderEqualTo("1");
Criteria criteria2 = example.createCriteria();
criteria2.andEmailLike("%e%");
example.or(criteria2);
List<Employee> list = mapper.selectByExample(example);
for (Employee employee : list) {
System.out.println(employee.getId());
}
}finally{
openSession.close();
}
}
MyBatis在四大对象Executor、ParameterHandler、ResultSetHandler、StatementHandler的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
① 每个创建出来的对象不是直接返回的,而是调用方法 interceptorChain.pluginAll(parameterHandler) 后返回。
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
② pluginAll() 方法获取到所有的Interceptor(拦截器,插件需要实现的接口)后,调用interceptor.plugin(target),即返回target包装后的对象。
③ 插件机制,我们可以使用插件为目标对象创建一个代理对象(即AOP,面向切面编程),我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行。
以入门案例的代码为基础来编写插件,其具体步骤如下:
① 编写Interceptor的实现类
② 使用@Intercepts注解完成插件签名
package com.atguigu.mybatis.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
//完成插件签名:告诉MyBatis当前插件用来拦截哪个对象的哪个方法
@Intercepts(
{
@Signature(type= StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor {
//intercept:拦截目标对象的目标方法的执行
@Override
public Object intercept(Invocation invocation) throws Throwable {
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return null;
}
//plugin:包装目标对象的,包装:为目标对象创建一个代理对象
@Override
public Object plugin(Object target) {
//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
Object wrap = Plugin.wrap(target, this);
//返回为当前target创建的动态代理
return wrap;
}
//setProperties:将插件注册时的property属性设置进来
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的信息:"+properties);
}
}
③ 将写好的插件注册到全局配置文件mybatis-config中
<plugins>
<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
plugin>
plugins>
④ 运行任意一个测试方法
@Test
public void test02() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(1);
System.out.println(mapper.getClass());
System.out.println(employee);
}finally{
openSession.close();
}
}
① 编写第二个插件
package com.atguigu.mybatis.dao;
import java.util.Properties;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
@Intercepts(
{
@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class MySecondPlugin implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MySecondPlugin...intercept:"+invocation.getMethod());
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// TODO Auto-generated method stub
System.out.println("MySecondPlugin...plugin:"+target);
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
}
}
② 注册插件
<plugins>
<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
plugin>
<plugin interceptor="com.atguigu.mybatis.dao.MySecondPlugin">plugin>
plugins>
@Override
public Object intercept(Invocation invocation) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
Object target = invocation.getTarget();
System.out.println("当前拦截到的对象:"+target);
//拿到:StatementHandler==>ParameterHandler===>parameterObject
//拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql语句用的参数是:"+value);
//修改完sql语句要用的参数
metaObject.setValue("parameterHandler.parameterObject", 3);
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
① PageHelper插件官网地址
② 以入门案例的代码为例,在其基础上使用PageHelper插件。
1)导入需要的 jar 包
2)在全局配置文件mybatis-config中注册pageHelper插件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>
3)测试使用
@Test
public void testPageHelper01() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
//查询第1页的5条记录
Page<Object> page = PageHelper.startPage(1, 5);
List<Employee> emps = mapper.getEmps();
for (Employee emp : emps) {
System.out.println(emp);
}
System.out.println("当前页码:"+page.getPageNum());
System.out.println("总记录数:"+page.getTotal());
System.out.println("每页的记录数:"+page.getPageSize());
}finally{
openSession.close();
}
}
@Test
public void testPageHelper02() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
//查询第5页的1条记录
Page<Object> page = PageHelper.startPage(5, 1);
List<Employee> emps = mapper.getEmps();
//传入要连续显示多少页
PageInfo<Employee> info = new PageInfo<>(emps, 5);
for (Employee employee : emps) {
System.out.println(employee);
}
System.out.println("当前页码:"+info.getPageNum());
System.out.println("总记录数:"+info.getTotal());
System.out.println("每页的记录数:"+info.getPageSize());
System.out.println("总页码:"+info.getPages());
System.out.println("是否第一页:"+info.isIsFirstPage());
int[] nums = info.getNavigatepageNums();
System.out.println("连续显示的页码:");
//当前页码为第5页,在连续显示时,当前页码居中
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
} finally {
openSession.close();
}
}
以入门案例的代码为例,在其基础上进行批量操作。
① 在EmployeeMapper.java中添加抽象方法
Long addEmp(Employee employee);
② 在EmployeeMapper.xml编写对应的sql语句
<insert id="addEmp">
insert into tbl_employee(last_name, gender, email)
values (#{last_name},#{gender},#{email})
insert>
③ 测试
@Test
public void testBatch() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//可以执行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
long start = System.currentTimeMillis();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
//插入1000条数据
for (int i = 0; i < 1000; i++) {
String last_name = UUID.randomUUID().toString().substring(0, 5);
mapper.addEmp(new Employee(last_name, last_name+"@163.com", "1"));
}
openSession.commit();
long end = System.currentTimeMillis();
//批量:(预编译sql一次==>设置参数===>10000次===>执行(1次))
//Parameters: 616c1(String), [email protected](String), 1(String)==>4598
//非批量:(预编译sql=设置参数=执行)==》10000 10200
System.out.println("执行时长:"+(end-start)+"毫秒");
}finally{
openSession.close();
}
}
④ 与Spring整合中,推荐额外的配置一个可以专门用来执行批量操作的sqlSession
<bean id="sqlSession" class="org.mybatis.spring.sqLSessionTemplate">
<constructor-arg name="sqLSessionFactory" ref= "sqLSessionFactoryBean">constructor-arg>
<constructor-arg name="executorType" value="BATCH">constructor-arg>
bean>
需要用到批量操作的时候,可以注入配置的这个批量SqlSession,通过它获取到mapper映射器进行操作即可。
① 创建枚举类EmpStatus,然后在Employee.java中将该类作为其属性并构建 get/set 方法。
package com.atguigu.mybatis.bean;
public enum EmpStatus {
//员工状态
LOGIN,LOGOUT,REMOVE
}
//员工状态
private EmpStatus empStatus=EmpStatus.LOGOUT;
② 在表tbl_employee中新增一列,表示员工状态,同时也需要改变aEmployeeMapper.xml中对应的sql语句
alter table tbl_employee add empStatus varchar(11)
<insert id="addEmp">
insert into tbl_employee(last_name, gender, email,empStatus)
values (#{last_name},#{gender},#{email},#{empStatus})
insert>
③ 测试
/*
* 默认mybatis在处理枚举对象的时候保存的是枚举的名字:EnumTypeHandler
* 改变使用:EnumOrdinalTypeHandler:
*/
@Test
public void testEnum() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = new Employee("test_enum", "[email protected]","1");
mapper.addEmp(employee);
openSession.commit();
}finally{
openSession.close();
}
}
在全局配置文件mybatis-config.xml添加如下配置之后,再次测试时便保存枚举的索引
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.atguigu.mybatis.bean.EmpStatus"/>
typeHandlers>
① 重新编写枚举EmpStatus类
package com.atguigu.mybatis.bean;
//希望数据库保存的是100,200这些状态码,而不是默认0,1或者枚举的名
public enum EmpStatus {
LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在");
private Integer code;
private String msg;
private EmpStatus(Integer code,String msg){
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
//按照状态码返回枚举对象
public static EmpStatus getEmpStatusByCode(Integer code){
switch (code) {
case 100:
return LOGIN;
case 200:
return LOGOUT;
case 300:
return REMOVE;
default:
return LOGOUT;
}
}
}
② 自定义TypeHandler类
package com.atguigu.mybatis.typehandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import com.atguigu.mybatis.bean.EmpStatus;
//1、实现TypeHandler接口,或者继承BaseTypeHandler
public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {
//定义当前数据如何保存到数据库中
@Override
public void setParameter(PreparedStatement ps, int i, EmpStatus parameter,
JdbcType jdbcType) throws SQLException {
// TODO Auto-generated method stub
System.out.println("要保存的状态码:"+parameter.getCode());
ps.setString(i, parameter.getCode().toString());
}
@Override
public EmpStatus getResult(ResultSet rs, String columnName)
throws SQLException {
// TODO Auto-generated method stub
//需要根据从数据库中拿到的枚举的状态码返回一个枚举对象
int code = rs.getInt(columnName);
System.out.println("从数据库中获取的状态码:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
@Override
public EmpStatus getResult(ResultSet rs, int columnIndex)
throws SQLException {
// TODO Auto-generated method stub
int code = rs.getInt(columnIndex);
System.out.println("从数据库中获取的状态码:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
@Override
public EmpStatus getResult(CallableStatement cs, int columnIndex)
throws SQLException {
// TODO Auto-generated method stub
int code = cs.getInt(columnIndex);
System.out.println("从数据库中获取的状态码:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
}
③ 在全局配置文件mybatis-config.xml注册
<typeHandlers>
<typeHandler handler="com.atguigu.mybatis.typehandler.MyEnumEmpStatusTypeHandler" javaType="com.atguigu.mybatis.bean.EmpStatus"/>
typeHandlers>