MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
主要就是简化了jdbc操作,能够更方便的操作数据库。
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
提供映射标签,支持对象与数据库的orm字段关系映射
提供对象关系映射标签,支持对象关系组建维护
提供xml标签,支持编写动态sql。
这里以查询员工表信息为例子
a>引入依赖
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.21version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
b>创建实体类
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
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='" + lastName + '\'' +
", email='" + email + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
c>创建全局配置文件mybatis-config
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT" />
<property name="username" value="root" />
<property name="password" value="199787" />
dataSource>
environment>
environments>
<mappers>
<mapper resource="EmployeeMapper.xml" />
mappers>
configuration>
d>编写sql映射文件EmployeeMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="abc">
<select id="getEmpById" resultType="com.qy.mybatis.Employee">
select id,last_name lastName,email,gender from employee where id = #{id}
select>
mapper>
e>测试
import com.qy.mybatis.Employee;
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 test {
/**
* 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 有数据源一些运行环境信息
* 2、sql映射文件;配置了每一个sql,以及sql的封装规则等。
* 3、将sql映射文件注册在全局配置文件中
* 4、写代码:
* 1)、根据全局配置文件得到SqlSessionFactory;
* 2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
* 一个sqlSession就是代表和数据库的一次会话,用完关闭
* 3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
*
* @throws IOException
*/
public SqlSessionFactory getSqlSessionFactory() throws IOException, IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test1() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try {
Employee employee = openSession.selectOne(
"abc.getEmpById", 1);
System.out.println(employee);
} finally {
openSession.close();
}
}
}
以上方法,我们在调用的使用,需要加上命名空间+sql唯一标注很不方便。我们可以才有接口式编程
a>创建一个Dao接口
public interface EmployeeDao {
Employee getEmpById(Integer id);
}
b>sql映射文件的命名空间指向这个接口并且sql的id和方法名保持一致
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qy.mybatis.dao.EmployeeDao">
<select id="getEmpById" resultType="com.qy.mybatis.entity.Employee">
select id,last_name lastName,email,gender from employee where id = #{id}
select>
mapper>
c>测试
@Test
public void test2() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class);//这里是获取的一个代理对象
Employee employee= employeeDao.getEmpById(1);
System.out.println(employee);
}
小结:
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抽取出来。
<properties resource="dbConfig.properties">properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
<configuration>
...
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
<typeAlias alias="Blog" type="domain.blog.Blog"/>
<typeAlias alias="Comment" type="domain.blog.Comment"/>
<typeAlias alias="Post" type="domain.blog.Post"/>
<typeAlias alias="Section" type="domain.blog.Section"/>
<typeAlias alias="Tag" type="domain.blog.Tag"/>
typeAliases>
configuration>
无论是 MyBatis 在预处理语句( PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-klVEXZS6-1650933511429)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220217204655780.png)]
插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。 插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。
了解mybatis运行原理才能更好开发插件。
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
<property name="MySQL" value="mysql" />
databaseIdProvider>
DB_VENDOR - 会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短
databaseId属性在映射xml使用
<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
databaseId="mysql">
select * from employee where id = #{id}
select>
<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
databaseId="oracle">
select e.* from employee e where id = #{id}
select>
切换数据库,便能切换SQL。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
mappers>
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
mappers>
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
mappers>
<mappers>
<package name="org.mybatis.builder"/>
mappers>
映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义;
cache –命名空间的二级缓存配置
cache-ref – 其他命名空间缓存配置的引用。
resultMap – 自定义结果集映射
parameterMap – 已废弃!老式风格的参数映射
sql –抽取可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句
1.dao接口编写
public interface EmployeeDao {
Employee getEmpById(Integer id);
Integer addEmp(Employee employee);
Integer updateEmp(Employee employee);
Integer deleteEmp(Integer id);
}
2.mapper映射文件编写
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qy.mybatis.dao.EmployeeDao">
<select id="getEmpById" resultType="com.qy.mybatis.entity.Employee">
select id,last_name ,email,gender from employee where id = #{id}
select>
<insert id="addEmp" parameterType="com.qy.mybatis.entity.Employee">
insert into employee(last_name,email,gender)
values(#{lastName},#{email},#{gender})
insert>
<update id="updateEmp" parameterType="com.qy.mybatis.entity.Employee">
update employee
set
last_name = #{lastName}
where
id = #{id}
update>
<delete id="deleteEmp" parameterType="com.qy.mybatis.entity.Employee">
delete from employee
where
id = #{id}
delete>
mapper>
3.测试
@Test
public void test3() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class);
Employee addEmployee = new Employee();
addEmployee.setLastName("小王");
addEmployee.setGender("0");
addEmployee.setEmail("[email protected]");
Integer integer = employeeDao.addEmp(addEmployee);
System.out.println(integer);
Employee updateEmployee = new Employee();
updateEmployee.setId(2);
updateEmployee.setLastName("qianyue");
updateEmployee.setEmail("[email protected]");
updateEmployee.setGender("0");
employeeDao.updateEmp(updateEmployee);
employeeDao.deleteEmp(1);
openSession.commit();
}
注意:
mybatis允许增删改直接定义以下类型返回值
Integer、Long、Boolean、void
我们需要手动提交数据
sqlSessionFactory.openSession();=》手动提交
sqlSessionFactory.openSession(true);=》自动提交
获取自增主键的值:
1.mysql
mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys();
useGeneratedKeys=“true”;使用自增主键获取主键值策略
keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
代码演示
<insert id="addEmp" parameterType="com.qy.mybatis.entity.Employee" useGeneratedKeys="true" keyProperty="id">
insert into employee(last_name,email,gender)
values(#{lastName},#{email},#{gender})
insert>
这样我们可以通过插入的这个类对象获取主键值
2.Oracle
#从序列获取新主键值
select employee_seq.nextval from dual;
oracle 通过selectKey 获取序列值
<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不会做特殊处理 #{参数名/任意值}取值
多个参数:mybatis会做特殊处理
通常操作
上述操作会抛出异常Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter ‘id’ not found.
多个参数会被mybatis拼接为一个map
#{} 获取指定key的值
命名参数:明确指定封装参数时map的key;@Param(“id”)
Employee getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);
POJO/MAP/TO
POJO 如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo
Map 如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
TO 如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象,如:’
Page{
int index;
int size;
}
扩展思考
public Employee getEmp(@Param(“id”)Integer id,String lastName);
public Employee getEmp(Integer id,@Param(“e”)Employee emp);
如果参数是Collection(List、Set)类型或者是数组,
源码分析
EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class);获取的是代理对象
(@Param(“id”)Integer id,@Param(“lastName”)String lastName);
ParamNameResolver类中的getNamedParams解析参数封装map的;
names:{0=id, 1=lastName};构造器的时候就确定好了
1.获取每个标了param注解的参数的@Param的值:id,lastName; 赋值给name;
2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)
name的值:
标注了param注解:注解的值
没有标注:
1.全局配置:useActualParamName(jdk1.8):name=参数名
2.name=map.size();相当于当前元素的索引
{0=id, 1=lastName,2=2}
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
#{}和${}都可以获取map或实体类中的值
区别:
#{}
: 是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入${}
: 取出的值直接拼装在sql语句中;会有安全问题;大多情况下,我们去参数的值都应该去使用#{}
。
原生jdbc不支持占位符的地方我们就可以使用${}
进行取值,比如分表、排序。。。;按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
什么是sql注入
#{}
:更丰富的用法:
规定参数的一些规则:
jdbcType通常需要在某种特定的条件下被设置:
在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle DB(报错);
JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,Oracle DB不能正确处理;由于全局配置中:jdbcTypeForNull=OTHER,Oracle DB不支持,两种解决方法:
#{email,jdbcType=NULL}
;
<select id="getEmpList" resultType="com.qy.mybatis.entity.Employee">
select * from employee
select>
//返回多条通过@MapKey指定返回的map中key对应的字段
@MapKey("lastName")
Map<String,Employee> getEmpReturnMap();
//返回一条记录的map;key就是列名,值就是对应的值
public Map<String, Object> getEmpByIdReturnMap(Integer id);
<select id="getEmpReturnMap" resultType="com.qy.mybatis.entity.Employee">
select * from employee
select>
<resultMap id="empMap" type="com.qy.mybatis.entity.Employee">
<id column="id" property="id">id>
<result column="last_name" property="lastName">result>
<result column="gender">result>
<result column="email" property="email">result>
resultMap>
<select id="getEmpByIdWithResultMap" resultMap="empMap">
select id,last_name ,email,gender from employee where id = #{id}
select>
部门和员工实体类搭建
public class Department {
private Integer id;
private String departmentName;
private List<Employee> empList;
}
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
private Department department;
}
数据库中 员工表的外键department_id是部门表的主键
a>类中嵌套类
员工和部门之间的关系
查询员工信息的时候把部门名称也查询出来
(1)普通级联映射
<resultMap id="getEmpAndDeptResult" type="com.qy.mybatis.entity.Employee">
<id column="id" property="id">id>
<result column="last_name" property="lastName">result>
<result column="gender" property="gender">result>
<result column="email" property="email">result>
<result column="d_id" property="department.id">result>
<result column="department_name" property="department.departmentName">result>
resultMap>
<select id="getEmpAndDept" resultMap="getEmpAndDeptResult">
select
e.id id,
e.last_name lastName,
e.gender gender,
e.email email,
e.department_id department_id,
d.id d_id,
d.department_name department_name
from employee e ,department d
where
e.department_id = d.id
and e.id=#{id}
select>
(2)association定义关联对象封装规则
<resultMap id="getEmpAndDeptByAssociationResult" type="com.qy.mybatis.entity.Employee">
<id column="id" property="id">id>
<result column="lastName" property="lastName">result>
<result column="gender" property="gender">result>
<result column="email" property="email">result>
<association property="department" javaType="com.qy.mybatis.entity.Department">
<id column="d_id" property="id">id>
<result column="department_name" property="departmentName">result>
association>
resultMap>
<select id="getEmpAndDeptByAssociation" resultMap="getEmpAndDeptByAssociationResult">
select
e.id id,
e.last_name lastName,
e.gender gender,
e.email email,
e.department_id department_id,
d.id d_id,
d.department_name department_name
from employee e ,department d
where
e.department_id = d.id
and e.id=#{id}
select>
(3)association分步查询
DepartmentDao.java
public interface DepartmentDao {
Department getDeptById(Integer id);
}
DepartmentMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qy.mybatis.dao.DepartmentDao">
<select id="getDeptById" resultType="com.qy.mybatis.entity.Department" >
select id,department_name departmentName from department where id = #{id}
select>
mapper>
mybatis-config.xml
<mappers>
<mapper resource="EmployeeMapper.xml" />
<mapper resource="DepartmentMapper.xml" />
mappers>
EmployeeMapper.xml
<resultMap id="gerEmpByIdStepResult" type="com.qy.mybatis.entity.Employee">
<id column="id" property="id">id>
<result column="lastName" property="lastName">result>
<result column="gender" property="gender">result>
<result column="email" property="email">result>
<association property="department" select="com.qy.mybatis.dao.DepartmentDao.getDeptById" column="department_id">
association>
resultMap>
<select id="getEmpByIdStep" resultMap="gerEmpByIdStepResult">
select
e.id id,
e.last_name lastName,
e.gender gender,
e.email email,
e.department_id department_id
from employee e
where
e.id=#{id}
select>
(4)分步查询&延迟加载
我们每次查询Employee对象的时候,都将一起查询出来。部门信息在我们使用的时候再去查询;分段查询的基础之上加上两个配置:
在全局配置文件中配置,实现懒加载
mybatis-config.xml
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
(5)collection封装结果
比如 我们要查询一个部门下的所有员工
DepartmenDao
Department getDeptByIdPlus(Integer id);
DepartmenMapper.xml
<resultMap id="getDeptByIdPlusResult" type="com.qy.mybatis.entity.Department">
<id column="id" property="id">id>
<result column="departmentName" property="departmentName">result>
<collection property="empList" ofType="com.qy.mybatis.entity.Employee">
<id column="e_id" property="id">id>
<result column="lastName" property="lastName">result>
<result column="gender" property="gender">result>
<result column="email" property="email">result>
collection>
resultMap>
<select id="getDeptByIdPlus" resultMap="getDeptByIdPlusResult">
select
d.id id,
d.department_name departmentName,
e.id e_id,
e.last_name lastName,
e.gender gender,
e.email email
from department d left join employee e on d.id = e.department_id
where d.id = 1
select>
(5)collection封装结果-延迟加载
DepartmentDao
Department getDeptByIdStep(Integer id);
DepartmentMapper
<resultMap id="getDeptByIdStepResult" type="com.qy.mybatis.entity.Department">
<id column="id" property="id">id>
<result column="departmentName" property="departmentName">result>
<collection property="empList" select="com.qy.mybatis.dao.EmployeeDao.getEmpListByDept" column="id">
collection>
resultMap>
<select id="getDeptByIdStep" resultMap="getDeptByIdStepResult" >
select id,department_name departmentName from department where id = #{id}
select>
(6)分步查询传递多列值&fetchType
column="{key1=column1,key2=column2}"
<resultMap id="getDeptByIdStepResult" type="com.qy.mybatis.entity.Department">
<id column="id" property="id">id>
<result column="departmentName" property="departmentName">result>
<collection property="empList" select="com.qy.mybatis.dao.EmployeeDao.getEmpListByDept" column="{deptId=id}" fetchType="lazy">
collection>
resultMap>
<select id="getDeptByIdStep" resultMap="getDeptByIdStepResult" >
select id,department_name departmentName from department where id = #{id}
select>
动态SQL是MyBatis强大特性之一。极大的简化我们拼装SQL的操作。
动态SQL元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。
MyBatis采用功能强大的基于 OGNL 的表达式来简化操作
if
choose (when, otherwise)
trim (where, set)
foreach
DynamicSQLMapper
public interface DynamicSQLMapper {
//携带了哪个字段查询条件就带上这个字段的值
public List<Employee> getEmpListByConditionIf(Employee employee);
}
DynamicSQLMapper.xml
<select id="getEmpListByConditionIf" resultType="com.qy.mybatis.entity.Employee">
select *
from employee
where 1=1
<if test="id!=null and id!=''">
and id=#{id}
if>
<if test="lastName!=null && lastName!=""">
and last_name like #{lastName}
if>
<if test="email!=null and email.trim()!=""">
and email=#{email}
if>
<if test="gender==0 or gender==1">
and gender=#{gender}
if>
select>
查询的时候如果某些条件没带可能sql拼装会有问题
1.给where后面加上1=1,以后的条件都and xxx。
2.mybatis使用where标签来将所有的查询条件包括在内。mybatis就会将where标签中拼装的sql,多出来的and或者or去掉(where只会去掉第一个多出来的and或者or,但最后一个多出来的and或者or则不会去掉。
DynamicSQLMapper
public List<Employee> getEmpListByConditionWhere(Employee employee);
DynamicSQLMapper.xml
<select id="getEmpListByConditionWhere" resultType="com.qy.mybatis.entity.Employee">
select *
from employee
<where>
<if test="id!=null and id!=''">
and id=#{id}
if>
<if test="lastName!=null && lastName!=""">
and last_name like #{lastName}
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>
trim标签 相关属性
-prefix 给拼串后的整个字符串加一个前缀
-prefixOverrides 去掉整个字符串前面多余的字符
-suffix 拼串后的整个字符串加一个后缀
-suffixOverrides 去掉整个字符串后面多余的字符
DynamicSQLMapper
public List<Employee> gerEmpListByConditionTrim(Employee employee);
DynamicSQLMapper.xml
<select id="gerEmpListByConditionTrim" resultType="com.qy.mybatis.entity.Employee">
select *
from employee
<trim prefix="where" prefixOverrides="and" suffixOverrides="and">
<if test="id!=null and id!=''">
and id=#{id}
if>
<if test="lastName!=null && lastName!=""">
and last_name like #{lastName}
if>
<if test="email!=null and email.trim()!=""">
and email=#{email}
if>
<if test="gender==0 or gender==1">
and gender=#{gender}
if>
trim>
select>
choose相当于 case when break 分支选择 选择其中的一个
DynamicSQLMapper
public List<Employee> getEmpListByConditionChoose(Employee employee);
DynamicSQLMapper.xml
<select id="getEmpListByConditionChoose" resultType="com.qy.mybatis.entity.Employee">
select *
from employee
<where>
<choose>
<when test="id!=null and id!=''">
id=#{id}
when>
<when test="lastName!=null and lastName!=''">
last_name like#{lastName}
when>
<otherwise>
gender=#{gender}
otherwise>
choose>
where>
select>
set标签中可以动态set值,更新我们需要更新的字段
DynamicSQLMapper
public void updateEmp(Employee employee);
DynamicSQLMapper.xml
<update id="updateEmp">
update employee
<set>
<if test="lastName!=null && lastName!=""">
last_name = #{lastName},
if>
<if test="email!=null and email.trim()!=""">
email=#{email},
if>
set>
where
id = #{id}
update>
collection:指定要遍历的集合:
list类型的参数会特殊处理封装在map中,map的key就叫list
item:将当前遍历出的元素赋值给指定的变量
separator:每个元素之间的分隔符
open:遍历出所有结果拼接一个开始的字符
close:遍历出所有结果拼接一个结束的字符
index:索引。遍历list的时候是index就是索引,item就是当前值
遍历map的时候index表示的就是map的key,item就是map的值
#{变量名}就能取出变量的值也就是当前遍历出的元素
DynamicSQLMapper
public List<Employee> getEmpListByConditionForeach(@Param("ids") List<Integer> ids);
DynamicSQLMapper.xml
<select id="getEmpListByConditionForeach" resultType="com.qy.mybatis.entity.Employee">
select *
from employee
<where>
id in
<foreach collection="ids" item="item" separator="," open="(" close=")">
#{item}
foreach>
where>
select>
mysql支持 insert 表名(字段) values (),(),()方式 和 insert 表名 values();insert 表名 values();insert 表名 values(); 两种方式
方式一:
DynamicSQLMapper
public void insertForeach1(@Param("employeeList") List<Employee> employeeList);
DynamicSQLMapper.xml
<insert id="insertForeach1">
insert employee(last_name,email,gender,department_id)
values
<foreach collection="employeeList" item="emp" separator="," >
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
foreach>
insert>
方式二
注意这种方式需要配置数据库连接url属性allowMultiQueries=true
jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&allowMultiQueries=true"
DynamicSQLMapper
public void insertForeach2(@Param("employeeList") List<Employee> employeeList);
DynamicSQLMapper.xml
<insert id="insertForeach2">
<foreach collection="employeeList" item="emp" separator=";" >
insert into employee(last_name,email,gender,department_id)
values(
#{emp.lastName},
#{emp.email},
#{emp.gender},
#{emp.department.id})
foreach>
insert>
Oracle数据库批量保存:
Oracle支持的批量方式:
# 多个insert放在begin - end里面
begin
insert into employees(employee_id,last_name,email)
values(employees_seq.nextval,'test_001','[email protected]');
insert into employees(employee_id,last_name,email)
values(employees_seq.nextval,'test_002','[email protected]');
end;
# 利用中间表
insert into employees(employee_id,last_name,email)
select employees_seq.nextval,lastName,email from(
select 'test_a_01' lastName,'test_a_e01' email from dual
union
select 'test_a_02' lastName,'test_a_e02' email from dual
union
select 'test_a_03' lastName,'test_a_e03' email from dual
);
方式一
<insert id="addEmps" databaseId="oracle">
<foreach collection="emps" item="emp" open="begin" close="end;">
insert into employees(employee_id,last_name,email)
values(employees_seq.nextval,#{emp.lastName},#{emp.email});
foreach>
insert>
方式二
<insert>
insert into employees(employee_id,last_name,email)
<foreach collection="emps" item="emp" separator="union"
open="select employees_seq.nextval,lastName,email from("
close=")">
select #{emp.lastName} lastName,#{emp.email} email from dual
foreach>
insert>
不只是方法传递过来的参数可以被用来判断,
mybatis默认还有两个内置参数:
_parameter:代表整个参数
单个参数:_parameter就是这个参数
多个参数:参数会被封装为一个map;_parameter就是代表这个map
_databaseId:如果配置了databaseIdProvider标签。
_databaseId就是代表当前数据库的别名oracle
<select id="getEmpsTestInnerParameter" resultType="com.lun.c01.helloworld.bean.Employee">
<if test="_databaseId=='mysql'">
select * from tbl_employee
<if test="_parameter!=null">
where last_name like #{_parameter.lastName}
if>
if>
<if test="_databaseId=='oracle'">
select * from employees
<if test="_parameter!=null">
where last_name like #{_parameter.lastName}
if>
if>
select>
<select id="getEmpsTestInnerParameter" resultType="com.atguigu.mybatis.bean.Employee">
<bind name="lastName" value="'%'+lastName+'%'"/>
<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 employees
<if test="_parameter!=null">
where last_name like #{_parameter.lastName}
if>
if>
select>
抽取可重用的sql片段。方便后面引用:
sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用
include来引用已经抽取的sql:
include还可以自定义一些property,sql标签内部就能使用自定义的属性
include-property:取值的正确方式${prop} 不能使用#
DynamicSQLMapper
public List<Employee> getEmpListBySQL();
DynamicSQLMapper.xml
<select id="getEmpListBySQL" resultType="com.qy.mybatis.entity.Employee">
select
<include refid="selectSql">include>
from employee
select>
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。
默认情况下,只有一级缓存( SqlSession级别的缓存,也称为本地缓存)开启。
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
为了提高扩展性。 MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
一级缓存(local cache),即本地缓存,作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。
本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域.
在mybatis3.1之后, 可以配置本地缓存的作用域. 在 mybatis.xml 中配置
同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中
key = hashCode + 查询的SqlId + 编写的sql查询语句 + 参数
一级缓存失效的四种情况:
不同的SqlSession对应不同的一级缓存
同一个SqlSession但是查询条件不同
同一个SqlSession两次查询期间执行了任何一次增删改操作
同一个SqlSession两次查询期间手动清空了缓存
二级缓存(second level cache),全局作用域缓存
二级缓存默认不开启,需要手动配置
MyBatis提供二级缓存的接口以及实现,缓存实现要求 POJO实现Serializable接口
二级缓存在 SqlSession 关闭或提交之后才会生效
使用步骤
cache标签的属性:
eviction:缓存的回收策略:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
false:非只读:mybatis觉得获取的数据可能会被修改。mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
type=“”:指定自定义缓存的全类名;
实现Cache接口即可;
tip:mybatis必须是会话关闭或者提交后 一级缓存的数据才会转移到二级缓存中
代码演示
1.全局配置文件中配置二级缓存
<setting name="cacheEnabled" value="true"/>
2.mapper文件中添加cache标签
<cache>cache>
3.实体类实现序列号接口
public class Employee implements Serializable
4.测试
@Test
public void test22() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession1 = sqlSessionFactory.openSession();
SqlSession openSession2 = sqlSessionFactory.openSession();
try {
EmployeeDao mapper1 = openSession1.getMapper(EmployeeDao.class);
EmployeeDao mapper2 = openSession2.getMapper(EmployeeDao.class);
Employee empById1 = mapper1.getEmpById(2);
System.out.println(empById1);
/*先关闭1号会话*/
openSession1.close();
Employee empById2 = mapper2.getEmpById(2);
System.out.println(empById2);
openSession2.close();
}finally {
}
}
全局setting的cacheEnable:
– 配置二级缓存的开关。一级缓存一直是打开的。
select标签的useCache属性:
– 配置这个select是否使用二级缓存。一级缓存一直是使用的
每个增删改查标签的flushCache属性:
– 增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。
– 询默认flushCache=false。
sqlSession.clearCache():
– 只是用来清除一级缓存。
全局setting的localCacheScope:
-本地缓存作用域:SESSION:当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWcsJJ4P-1650933511434)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220224130400636.png)]
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.ibatis.cache;
import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
String getId();
void putObject(Object var1, Object var2);
Object getObject(Object var1);
Object removeObject(Object var1);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}
整合步骤
1.导入依赖
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.2.1version>
dependency>
2.编写ehcache.xml文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<diskStore path="D:\\ehcache" />
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
defaultCache>
ehcache>
3.mapper映射文件中添加cache标签 并指明type
<cache type="org.mybatis.caches.ehcache.EhcacheCache">cache>
另外:
参照缓存: 若想在命名空间中共享相同的缓存配置和实例。可以使用 cache-ref 元素来引用另外一个缓存。
<cache-ref namespace="com.qy.mybatis.dao.EmployeeDao" >cache-ref>
1.配置web.xml
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Applicationdisplay-name>
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springMVC.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
2.配置SpringMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--SpringMVC只是控制网站跳转逻辑 -->
<!-- 只扫描控制器 -->
<context:component-scan base-package="com.qy.ssm.controller" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!--SpringMVC注解驱动-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--默认Servlet处理器处理静态资源请求-->
<mvc:default-servlet-handler/>
<mvc:view-controller path="/index" view-name="index"></mvc:view-controller>
</beans>
3.配置applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
<context:component-scan base-package="com.qy.ssm">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
context:component-scan>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT" />
<property name="username" value="root" />
<property name="password" value="199787" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
bean>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="configLocation" value="classpath:/mybatis-config.xml">property>
<property name="mapperLocations" value="classpath:/mapper/*Mapper.xml">property>
bean>
<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>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.qy.ssm.dao">property>
bean>
beans>
4.配置mybatis-config.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="cacheEnabled" value="true"/>
settings>
configuration>
略 网上看攻略
1.创建sqlSessionFactory
2.openSession开启会话
3.获得mapper接口代理对象
4.调用代理对象的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vwyln8WX-1650933511435)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220225083751245.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifupe6TQ-1650933511436)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220225083804092.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4SFKbF56-1650933511436)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220225084202038.png)]
创建SQLSessionFactory流程
* 1.读取全局配置文件 作为输入流调用SqlSessionFactoryBuilder.build(inputStream)方法
* 2.build(inputStream)方法中调用重载的build(inputStream)方法
* 3.XMLConfigBuilder 创建XML解析器Xpath调用parse解析配置文件内容
* 4.parse方法中调用parseConfiguration(parser.evalNode("/configuration"))方法 解析<configuration>标签体内容
* 4.parseConfiguration方法读取全局配置,把配置加载到Configuration方中,通过 mapperElement(root.evalNode("mappers"));读取mapper映射文件
* 5.mapperElement中通过XMLMapperBuilder调用parse()解析mapper映射文件的内容
* 6.XMLMapperBuilder的parse()方法中通过configurationElement方法读取mapper文件内容
* 7.buildStatementFromContext 读取我们的sql语句
* 8.buildStatementFromContext方法中通过XMLStatementBuilder对象调用parseStatementNode读取增删改查方法中的
* 标签和sql,最后通过builderAssistant.addMappedStatement方法返回一个MappedStatement对象并加载到Configuration中
* 9.最后通过SqlSessionFactory build(Configuration config)返回一个DefaultSqlSessionFactory对象
创建SQLSessionFactory
解析文件中的每一个信息保存到Configuration中,返回包含Configuration 的DefaultSqlSessionFactory对象
MapperdStatement代表一个增删改查的方法的相信信息
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uMdnad4U-1650933511436)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220226111457810.png)]
openSession执行流程
/*
* 1.openSession() 中调用openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
* 2.openSessionFromDataSource()中获取配置信息 包括环境、事务管理器等 并创建了一个Executor对象 Executor executor = configuration.newExecutor(tx, execType);
* 3.newExecutor中根据executorType创建不同类型的Executor对象 包括BatchExecutor、ReuseExecutor、SimpleExecutor
如果设置了二级缓存配置 再对生成的Executor对象进行封装 生成二级缓存的Executor对象 executor = new CachingExecutor(executor);
* 4.创建好Executor后 又执行了 executor = (Executor) interceptorChain.pluginAll(executor); 过滤器执行链 和插件有关
* 5.最后把Executor对象和Configuration作为参数返回一个DefaultSqlSession对象
* */
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-voW4Sx3f-1650933511437)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220226113618841.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8j4UcFHO-1650933511437)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220228130749846.png)]
执行流程:
1.调用MapperProxy的invoke方法
2.在invoke方法中调用execute方法 参数为sqlSession对象和方法参数
3.execute方法中判断sql的类型(增删改查),然后进行paramNameResolver进行参数封装并执行sqlSession的selectOne方法
4.在DefaultSqlSession中selectOne方法中调用selectList方法,取出第一条
5.selectList方法中,获取configuration中的MapperStatement对象 获取执行方法的具体信息,然后再调用executor.query方法
6.如果设置了二级缓存调用CachingExecutor中的query方法,获取BoundSql,代表要执行的sql
7.如果缓存中没有数据,调用真正执行器的query方法调用queryFromDatabase,查出数据后把数据也放在本地缓存
8.执行doQuery方法 创建StatementHandler对象(PreparedStatementHandler 实现了StatementHandler接口)实例是PreparedStatementHandler 初始化过程中创建ParmeHandler和resultSetHandler
9.(StatementHandler) interceptorChain.pluginAll(statementHandler);
10.(ParameterHandler) interceptorChain.pluginAll(parameterHandler);
11.(ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
12.预编译sql尝试PreparedStatement对象
13.调用ParmeHandler设置参数
14.使用TypeHandler给sql与编写设置参数
15.调用PreparedStatementHandler的query方法执行sql使用ResultSetHandler处理结果,使用TypeHander获取value值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xNC839Pl-1650933511437)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220228190718782.png)]
根据配置文件(全局,sql映射)初始化出Configuration对象
创建一个DefaultSqlSession对象,它里面包含Configuration以及Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
MapperProxy里面有(DefaultSqlSession);
执行增删改查方法:
调用DefaultSqlSession的增删改查(Executor);
会创建一个StatementHandler对象。同时也会创建出ParameterHandler和ResultSetHandler)
调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数
调用StatementHandler的增删改查方法;
ResultSetHandler封装结果
注意:四大对象(Executor、ParameterHandler、ResultSetHandler)每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);
在四大对象创建的时候
每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
获取到所有的Interceptor(拦截器)(插件需要实现的接口);调用interceptor.plugin(target);返回target包装后的对象
插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)我们的插件可以为四大对象创建出代理对象;代理对象就可以拦截到四大对象的每一个执行;
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
插件开发的步骤
编写插件实现Interceptor接口,并使用 @Intercepts注解完成插件签名
package com.qy.mybatis.Interceptors;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
/*
* 拦截的对象,type为拦截哪个类,method拦截哪个方法,args:拦截参数 防止重载无法识别
* */
@Intercepts({
@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class FirstPlugin implements Interceptor {
/*
* 1.调用目标方法
*/
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("FirstPlugin-->intercept");
/*
*invocation.proceed()表示放行执行目标方法
* */
System.out.println("拦截到的对象是"+invocation.getTarget());
Object proceed = invocation.proceed();
return proceed;
}
/*
*包装目标对象,为目标对象创建一个代理对象
**/
public Object plugin(Object target) {
/*通过Plugin类的wrap返回一个代理对象*/
System.out.println("被包装的对象是"+target);
Object wrap = Plugin.wrap(target, this);
return wrap;
}
/*
*将插件注册时的property属性设置进来
*/
public void setProperties(Properties properties) {
System.out.println(properties);
}
}
在全局配置文件中注册插件
<plugins>
<plugin interceptor="com.qy.mybatis.Interceptors.FirstPlugin">
<property name="username" value="qianyue"/>
plugin>
plugins>
插件原理
按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
目标方法执行时依次从外到内执行插件的intercept方法。
多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值