一、Mybatis框架
1.概述
-
mybatis是什么?有什么特点?
它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它和数据库无关性较低。
- 什么是ORM?
Object Relation Mapping,对象关系映射。对象指的是Java对象,关系指得是数据库中的关系模型,对象关系映射,指的就是Java对象和数据库的关系模型之间建立一种对应关系。比如:用一个Java的Student类,去对应数据库中的一张student表,一个Student对象就对应student表中的一行数据,类中的属性和表中的列一一对应。
- 为什么Mybatis是半自动的ORM框架?
用Mybatis进行开发,需要手动编写SQL语句。而全自动的ORM框架,如hibernate,则不需要编写SQL语句。用hibernate开发,只需要定义好ORM映射,就可以直接进行CRUD操作了。由于Mybatis需要手写SQL语句,所以它有较高的灵活性,可以根据需要,自由地对SQL进行定制,也因为要手写SQL,当要切换数据库时,SQL语句可能就要重写,因为不同的数据库有不同的方言(Dialet),所以Mybatis的数据库无关性比较低。虽然Mybatis需要手写SQL,但相比JDBC,他提供了输入映射和输出映射,可以很方便地进行SQL参数设置,以及结果集封装。并且还提供了关联查询和动态SQL等功能,极大地提升了开发效率。
2.原理示意图
-
Mybatis配置
SQLMapConfig.xml,此文件作为Mybatis的全局配置文件,配置了Mybatis的运行环境等信息,此后会有详细介绍。mapper.xml文件即SQL映射文件,文件中配置了操作数据库的SQL语句。此文件需要在SQLMapConfig.xml中加载。
-
通过Mybatis环境等配置信息构造SqlSessionFactory,即会话工厂
-
有会话工厂(SqlSessionFactory)创建sqlSession即会话,操作数据库需要通过sqlSession进行。
-
Mybatis底层自定义了Executor执行器接口来操作数据库,Executor接口有两个实现,一个是基本执行器,一个是缓存执行器
-
Mybatis底层自定义了Executor执行器接口来操作数据库,Executor接口有两个实现,一个是基本执行器,一个是缓存执行器
-
Mapped Statement也是Mybatis一个底层封装对象,它包含了Mybatis配置信息以及SQL映射信息等。mapper.xml文件中一个SQL对应一个Maped Statement对象,SQL的id即是Mapped Statement的id。
-
Mapped Statement对SQL输入参数进行定义,包含HashMap、基本数据类型、POJO等,Executor通过Mapped Statement在执行SQL前,将输入的java对象映射到SQL中,输入参数映射就是在JDBC编程中对PreparedStatement设置参数。
-
Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo等,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果集的解析处理过程
二、入门程序
1.原生开发
1.在jdbc数据库中,创建一张tbl_employee表(d_id字段暂时用不到)和tbl_dept表(本阶段暂时用不到此表)
2.打开IDEA,创建一个maven项目
3.导入依赖jar包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wangsuhang</groupId>
<artifactId>MyBatis3</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<!--静态资源导出问题-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.创建一个pojo类(一个映射类)
package com.wangsuhang.mybatis_01_helloworld.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author suahng
* @date 2021-07-06 9:56
* @dec
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
}
5.编写mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace: 名称空间;指定为接口的全类名(第二种方式这样写)
id:唯一标识,推荐使用mapper中接口的 具体 方法名
resultType:返回值类型
#{id}:从传递过来的参数中取出id值
-->
<mapper namespace="com.wangsuhang.mybatis_01_helloworld">
<select id="getEmpById" resultType="com.wangsuhang.mybatis_01_helloworld.pojo.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
</mapper>
6.编写全局配置文件SQLMapConfig.xml( mybatis-config.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--settings:控制mybatis全局行为-->
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--设置驼峰命名-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbc"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--sql mapper(SQL映射文件)的位置-->
<mappers>
<!--就是上一步的mappe.xml文件-->
<mapper resource="EmployeeMapper.xml" />
</mappers>
</configuration>
7.测试代码和运行结果
package com.wangsuhang.mybatis_01_helloworld.test;
import com.wangsuhang.mybatis_01_helloworld.mapper.EmployeeMapper;
import com.wangsuhang.mybatis_01_helloworld.pojo.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 MyBatisTest {
/**
* 1.根据xml配置文件(全局配置文件 mybatis-config.xml )创建一个 SqlSessionFactory 对象
* 有数据源的一些信息
* 2.sql映射文件(EmployeeMapper2.xml):配置了每一个sql,以及 sql 的封装规则
* 3.将 sql 映射文件注册在全局配置文件中
* 4.在测试类中写具体的代码:
* 1)根据全局配置文件得到 SQLSessionFactory
* 2)使用 SQLSession工厂,获取 sqlSession 对象,使用他来执行增删改查
* 一个 sqlSession 就是代表和数据库的一个会话,用完关闭
* 3)使用 sql 的唯一标识来告诉 MyBatis执行哪个SQL。 sql都是保存在sql映射文件中
*/
@Test
public void testMyBatis() throws IOException {
//1.获取sqlSessionFactory对象
String config = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(config);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
System.out.println(sqlSessionFactory);
//2.获取 sqlSession 实例,能直接执行已经映射的 sql 语句
SqlSession sqlSession = sqlSessionFactory.openSession();
//sql的唯一标识:statement- Unique identifier matching the statement to use.
//即mapper.xml文件中的namespace + SQL语句的id
//执行SQL要用的参数:parameter- A parameter object to pass to the statement.
Employee employee = sqlSession.selectOne("com.wangsuhang.mybatis_01_helloworld.getEmpById", 1);
System.out.println(employee);
//关闭资源
sqlSession.close();
}
}
运行结果
com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 com.wangsuhang.mybatis_01_helloworld.test.MyBatisTest,testMyBatis
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@f6c48ac
Opening JDBC Connection
Created connection 81009902.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d41cee]
==> Preparing: select id,last_name lastName,email,gender from tbl_employee where id = ?
==> Parameters: 1(Integer)
<== Columns: id, lastName, email, gender
<== Row: 1, admin, jerry@qq.com, 女
<== Total: 1
Employee(id=1, lastName=admin, email=jerry@qq.com, gender=女)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d41cee]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4d41cee]
Returned connection 81009902 to pool.
Process finished with exit code 0
2.Mapper动态代理方式开发DAO
1.实现原理
Mapper接口开发放肆只需要程序员编写Mapper接口(相当于DAO接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同DAO接口的实现类方法。
Mapper接口开发需要遵循以下规范:
- mapper.xml文件中namespace与mapper的全类名相同。
- Mapper接口方法名和mapper.xml中定义的每个statement(即具体的SQL语句)的id相同。
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同。
- Mapper接口方法的输出类型参数类型和mapper.xml中定义的每个resultType的类型相同。
2.改写mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace: 名称空间;指定为接口的全类名
id:唯一标识,推荐使用mapper中接口的 具体 方法名
resultType:返回值类型
#{id}:从传递过来的参数中取出id值
-->
<mapper namespace="com.wangsuhang.mybatis_01_helloworld.mapper.EmployeeMapper">
<select id="getEmpById" resultType="com.wangsuhang.mybatis_01_helloworld.pojo.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
</mapper>
3.编写XxxMapper.java(接口文件)
package com.wangsuhang.mybatis_01_helloworld.mapper;
import com.wangsuhang.mybatis_01_helloworld.pojo.Employee;
/**
* @author suahng
* @date 2021-07-06 14:18
* @dec
*/
public interface EmployeeMapper {
public Employee getEmpById(Integer id);
}
4.测试代码和运行结果
package com.wangsuhang.mybatis_01_helloworld.test;
import com.wangsuhang.mybatis_01_helloworld.mapper.EmployeeMapper;
import com.wangsuhang.mybatis_01_helloworld.pojo.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;
/**
* @author suahng
* @date 2021-07-06 10:49
* @dec
*
* 1.接口式编程
* 原生: Dao ====> DaoImpl
* mybatis: Mapper ====> xxMapper.xml
* 2.SqlSession:代表和数据库的一次会话,用完必须关闭
* 3.SqlSession和Connection 一样它都是非线程安全。每次使用都应该获取新的对象
* 4.mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象
* (将接口和xml进行绑定)
* EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
* 5.两个重要的配置文件:
* mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息的那个...系统运行环境信息
* sql映射文件:保存了每一个sql语句的映射信息,将sql抽取了出来了
*/
public class MyBatisTest {
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String config = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(config);
return new SqlSessionFactoryBuilder().build(is);
}
/**
* 1.获取sqlSessionFactory对象
* 解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSessionFactory
* 注意:MappedStatement:代表一个增删改查的详细信息
* 2.获取SQLSession对象
* 返回一个DefaultSqlSession对象,包含Executor和Configuration
* 这一步会创建Executor对象;
* 3.获取接口的代理对象(MapperProxy)
* getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象
* 代理对象里面包含了,DefaultSqlSession(Executor)
*
* 4.执行增删改操作
*
*
* 总结:
* 1.根据配置文件(全局,sql映射)初试化出Configuration对象
* 2.创建一个DefaultSqlSession对象,
* 他里面包含Configuration以及
* Executor(根据全局配置文件的defaultExecutorType创建出对应的Executor)
* 3.DefaultSqlSession.getMapper(),拿到Mapper接口对应的MapperProxy
* 4.MapperProxy里面有(DefaultSqlSession);
* 5.执行增删改查方法:
* 1)调用DefaultSession的增删改查(Executor);
* 2)会创建一个StatementHandler对象;(同时也会创建出ParameterHandler和ResultSetHandler)
* 3)调用StatementHandler预编译参数以及设置参数值;
* 使用ParameterHandler来给sql设置参数
* 4)调用StatementHandler的增删改查方法;
* 5)ResultSetHandler封装结果
* 注意:
* 四大对象(Executor,ParameterHandler,ResultSetHandler,StatementHandler)
* 每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler)
*
* @throws IOException
*/
@Test
public void test01() throws IOException {
//1.获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//2.获取SQLSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//3.获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
System.out.println(mapper.getClass());
Employee emp = mapper.getEmpById(1);
System.out.println(mapper.getClass());
System.out.println(emp);
}finally {
//4.关闭资源
sqlSession.close();
}
}
}
运行结果
com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 com.wangsuhang.mybatis_01_helloworld.test.MyBatisTest,test01
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
class com.sun.proxy.$Proxy4
Opening JDBC Connection
Created connection 1039949752.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3dfc5fb8]
==> Preparing: select id,last_name lastName,email,gender from tbl_employee where id = ?
==> Parameters: 1(Integer)
<== Columns: id, lastName, email, gender
<== Row: 1, admin, jerry@qq.com, 女
<== Total: 1
class com.sun.proxy.$Proxy4
Employee(id=1, lastName=admin, email=jerry@qq.com, gender=女)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3dfc5fb8]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3dfc5fb8]
Returned connection 1039949752 to pool.
Process finished with exit code 0
三、全局配置文件
1.配置文件内容
全局配置文件中,各个标签要按照如下的顺序进行配置,因为Mybatis加载配置文件的原码中是按照这个顺序进行解析的。
<configuration>
<!-- 配置顺序如下
properties
settings
typeAliases
typeHandlers
objectFactory
plugins
environments
environment
transactionManager
dataSource
mappers
-->
</configuration>
各个标签说明如下:
-
properties(属性)
-
settings(全局配置参数)
-
typeAliases(类型别名)
-
typeHandlers(类型处理器)
-
objectFactory(对象工厂)
-
plugins(插件)
-
environments(环境集合属性对象)
- environment(环境子属性对象)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境子属性对象)
-
databaseIdProvider(数据库厂商标识)
-
mappers(映射器)
1.1properties(属性)
这些属性可以在外部进行配置,并可以进行动态替换。
在classpath下定义dbconfig.properties文件
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/jdbc?rewriteBatchedStatements=true
jdbc.driver=com.mysql.jdbc.Driver
全局配置文件中引用如下:
<properties resource="dbconfig.properties"/>
<!--
1.mybatis可以使用 properties 来引入外部properties 配置文件的内容
resource:引入类路径下的资源
url:引入网络路径或者磁盘路径下的资源
-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
- 首先读取在 properties 元素体内指定的属性。
- 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
- 最后读取作为==方法参数即(parameterType)==传递的属性,并覆盖之前读取过的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。
1.2settings(全局配置参数)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true | false | true |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 | true | false | false |
aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods )。 | true | false | false (在 3.4.1 及之前的版本中默认为 true) |
multipleResultSetsEnabled | 是否允许单个语句返回多结果集(需要数据库驱动支持)。 | true | false | true |
useColumnLabel | 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 | true | false | true |
useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 | true | false | False |
autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或未知属性类型)的行为。NONE : 不做任何反应WARNING : 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN )FAILING : 映射失败 (抛出 SqlSessionException ) | NONE, WARNING, FAILING | NONE |
defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 | 任意正整数 | 未设置 (null) |
defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 | 任意正整数 | 未设置 (null) |
defaultResultSetType | 指定语句默认的滚动策略。(新增于 3.5.2) | FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置) | 未设置 (null) |
safeRowBoundsEnabled | 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 | true | false | False |
safeResultHandlerEnabled | 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 | true | false | True |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true | false | False |
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 | SESSION | STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 | OTHER |
lazyLoadTriggerMethods | 指定对象的哪些方法触发一次延迟加载。 | 用逗号分隔的方法列表。 | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定动态 SQL 生成使用的默认脚本语言。 | 一个类型别名或全限定类名。 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) | 一个类型别名或全限定类名。 | org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。 | true | false | false |
returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 null 。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | 未设置 |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置 |
proxyFactory | 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 | CGLIB | JAVASSIST | JAVASSIST (MyBatis 3.3 以上) |
vfsImpl | 指定 VFS 的实现 | 自定义 VFS 的实现的类全限定名,以逗号分隔。 | 未设置 |
useActualParamName | 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) | true | false | true |
configurationFactory | 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) | 一个类型别名或完全限定类名。 | 未设置 |
shrinkWhitespacesInSql | 从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5) | true | false | false |
defaultSqlProviderType | Specifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type (or value ) attribute on sql provider annotation(e.g. @SelectProvider ), when these attribute was omitted. | A type alias or fully qualified class name | Not set |
一个配置完整的 settings 元素的示例如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
本例中使用的settings元素有:
<!--settings:控制mybatis全局行为-->
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--设置驼峰命名-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
1.3 typeAliases(类型别名)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:
<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>
当这样配置时,Blog
可以用在任何使用 domain.blog.Blog
的地方。
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
每一个在包 domain.blog
中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author
的别名为 author
;若有注解,则别名为其注解值。见下面的例子:
@Alias("author")
public class Author {
...
}
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
所以,我们在别名的时候,不要占用上面已有的别名,它们都是大小写不敏感的。
1.4typeHandlers(类型处理器)
MyBatis 在设置预处理语句(PreparedStatement)中的参数,或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。
理解:用于处理Java类型和Jdbc类型之间的转换,mybatis有许多内置的TypeHandler
<select id="getEmpById" parameterType="int" resultType="com.wangsuhang.mybatis_01_helloworld.pojo.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
1.5objectFactory(对象工厂)
mybatis会根据resultType
或resultMap
的属性来将查询得到的结果封装成对应的Java类,它有一个默认的DefaultObjectFactory,用于创建对象实例,这个标签用的也不多
1.6plugins(插件)
可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper分页插件,这些插件就是通过这个标签进行配置的。
<!-- PageHelper 分页插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
1.7environments(环境配置)
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置。
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境
所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:
- 每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
如果忽略了环境参数,那么将会加载默认环境,如下所示:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
environments 元素定义了如何配置环境。
environment:指定据具环境
- id:指定当前环境的唯一标识
- transactionManager:事务管理器
- dataSource:数据源
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
注意一些关键点:
- 默认使用的环境 ID(比如:default=“development”)。
- 每个 environment 元素定义的环境 ID(比如:id=“development”)。
- 事务管理器的配置(比如:type=“JDBC”)。
- 数据源的配置(比如:type=“POOLED”)。
默认环境和环境 ID 顾名思义, 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。
本例中使用如下:
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbc"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
1.8databaseIdProvider(数据库厂商标识)
MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId
属性。 MyBatis 会加载带有匹配当前数据库 databaseId
属性和所有不带 databaseId
属性的语句。 如果同时找到带有 databaseId
和不带 databaseId
的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider
即可:
<databaseIdProvider type="DB_VENDOR">
<!-- 为不同的数据库厂商起别名 -->
<property name="MySQL" value="mysql1"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
在mapper.xml中使用:
<select id="getEmpById" resultType="com.wangsuhang.mybatis_02_config.pojo.Employee"
databaseId="mysql1">
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
Mybatis匹配规则如下:
- 如果没有配置databaseIdProvider标签,那么databaseId=null;
- 如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上,则设置databaseId=配置指定的值,否则,依旧为null;
- Mybatis会加载不带databaseId属性和带有匹配当前数据库databaseId属性的所有语句。如果同时找到带有databaseId和不带databaseId的相同语句,则后者会被舍弃。
1.9mappers(映射器)
mapper配置的几种方法:
-
<mapper resource=" " />
使用相对于类路径的资源引用
<mappers> <mapper resource="EmployeeMapper3.xml" /> <mapper resource="EmployeeMapperPlus.xml" /> <mapper resource="DepartmentMapper.xml"/> </mappers>
-
<mapper url=" " />
使用完全限定资源定位符(UEL)
<mapper url="file:///F:\code\MyBatis3\src\main\resources\EmployeeMapper2.xml" />
-
<mapper class=" " />
使用映射器接口实现类的完全限定类名
注意:
1.有sql映射文件,映射文件名必须和接口同名,并且与接口统一目录下;
2.没有sql映射 文件,所有的sql都是利用注解写在接口上
<mappers> <mapper resource="EmployeeMapper2.xml" /> <mapper class="com.wangsuhang.mybatis_02_config.mapper.EmployeeMapperAnnotation"/> </mappers>
本例中使用的是第二种方式:
package com.wangsuhang.mybatis_02_config.mapper; import com.wangsuhang.mybatis_01_helloworld.pojo.Employee; import org.apache.ibatis.annotations.Select; /** * @author suahng * @date 2021-07-06 20:23 * @dec */ public interface EmployeeMapperAnnotation { @Select("select * from tbl_employee where id = #{id}") public Employee getEmpById(Integer id); }
-
<package name=" " />
将包内的映射器接口实现全部注册为映射器
注意:
此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中
<mappers> <package name="org.mybatis.builder"/> </mappers>
四、参数传递
1.输入参数
1.1单个参数
可以接收基本类型,对象类型,集合类型的值。这种情况下Mybatis可以直接使用这个参数,不需要经过任何处理。
dao接口:
package com.wangsuhang.mybatis_03_mapper.mapper;
import com.wangsuhang.mybatis_03_mapper.pojo.Employee;
/**
* @author suahng
* @date 2021-07-06 14:18
* @dec
*/
public interface EmployeeMapper {
public Employee getEmpById(Integer id);
}
mapper.xml文件中对应的sql语句:
<select id="getEmpById" resultType="com.wangsuhang.mybatis_03_mapper.pojo.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
测试程序及结果
@Test
public void test() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//1.获取到的SQLSession不会自动提交数据
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = mapper.getEmpById(1);
System.out.println(emp);
sqlSession.commit();
}finally {
sqlSession.close();
}
}
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select id,last_name lastName,email,gender from tbl_employee where id = ?
==> Parameters: 1(Integer)
<== Columns: id, lastName, email, gender
<== Row: 1, admin, jerry@qq.com, 女
<== Total: 1
Employee(id=1, lastName=admin, email=jerry@qq.com, gender=女)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
1.2.多个参数
1.2.1多个参数-按位置
任意多个参数,都会被Mybatis重新包装成一个Map传入。Map的key是param1,param2… 或arg0,arg1…;value就是参数的值;
dao接口:
List<Employee> getEmpByIdOrLastName(Integer id,String lastName);
mapper.xml文件中对应的sql语句:
<!-- Employee getEmpByIdOrLastName(Integer id,String lastName); -->
<select id="getEmpByIdOrLastName" resultType="com.wangsuhang.mybatis_03_mapper.pojo.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{param1} or last_name = #{arg1}
</select>
测试程序及结果:
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> emps = mapper.getEmpByIdOrLastName(1, "admin");
emps.forEach(System.out::println);
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 36333492.
Returned connection 36333492 to pool.
Opening JDBC Connection
Checked out connection 36333492 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@22a67b4]
==> Preparing: select id,last_name lastName,email,gender from tbl_employee where id = ? or last_name = ?
==> Parameters: 1(Integer), admin(String)
<== Columns: id, lastName, email, gender
<== Row: 1, admin, jerry@qq.com, 女
<== Total: 1
Employee(id=1, lastName=admin, email=jerry@qq.com, gender=女)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@22a67b4]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@22a67b4]
Returned connection 36333492 to pool.
1.2.2多个参数-使用@Param
为参数使用@param起一个名字,Mybatis就会将这些参数封装进map中,key就是我们自己指定的名字。
即在方法中形参前面加入@param(“自定义参数”),mapper.xml文件中使用#{自定义参数名}
dao接口:
public Employee getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);
mapper.xml文件中对应的sql语句:
<select id="getEmpByIdAndLastName" resultType="com.wangsuhang.mybatis_03_mapper.pojo.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id} and last_name = #{lastName}
</select>
测试程序及结果:
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp = mapper.getEmpByIdAndLastName(1, "admin");
System.out.println(emp);
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select id,last_name lastName,email,gender from tbl_employee where id = ? and last_name = ?
==> Parameters: 1(Integer), admin(String)
<== Columns: id, lastName, email, gender
<== Row: 1, admin, jerry@qq.com, 女
<== Total: 1
Employee(id=1, lastName=admin, email=jerry@qq.com, gender=女)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
1.2.3多个参数-使用对象(POJO)
当这些参数属于我们业务在中的POJO时,我们直接传递POJO。
使用java对象传递参数,java的属性值就是SQL语句需要的参数值。每个属性就是一个参数。
语法格式:#{property,javaType=java中数据类型名,jdbcType=数据库中数据类型名};javaType,jdbcType的类型Mybatis可以检测出来,一般不需要设置,常用格式为#{property};
dao接口:
public Employee getEmpbyPojo(Employee employee);
mapper.xml文件中对应的sql语句:
<select id="getEmpbyPojo" parameterType="com.wangsuhang.mybatis_03_mapper.pojo.Employee"
resultType="com.wangsuhang.mybatis_03_mapper.pojo.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id} or last_name = #{lastName}
</select>
测试程序及结果:
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setId(1);
employee.setLastName("admin");
Employee emp = mapper.getEmpbyPojo(employee);
System.out.println(emp);
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select id,last_name lastName,email,gender from tbl_employee where id = ? or last_name = ?
==> Parameters: 1(Integer), admin(String)
<== Columns: id, lastName, email, gender
<== Row: 1, admin, jerry@qq.com, 女
<== Total: 1
Employee(id=1, lastName=admin, email=jerry@qq.com, gender=女)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
异常测试:
将SQL语句中字段名输入错误后测试,将lastName改为lastname:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'lastname' in 'class com.wangsuhang.mybatis_03_mapper.pojo.Employee'
### Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'lastname' in 'class com.wangsuhang.mybatis_03_mapper.pojo.Employee'
Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'lastname' in 'class com.wangsuhang.mybatis_03_mapper.pojo.Employee'
Process finished with exit code -1
1.2.4多个参数-使用map
也可将多个参数封装为map,直接传递,mapper.xml文件使用#{key}引用参数值。
dao接口:
public Employee getEmpByMap(Map<String,Object> map);
mapper.xml文件中对应的sql语句:
<select id="getEmpByMap" resultType="com.wangsuhang.mybatis_03_mapper.pojo.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id} and last_name = #{lastName}
</select>
测试程序及结果:
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("id",1);
map.put("lastName","admin");
Employee emp = mapper.getEmpByMap(map);
System.out.println(emp);
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select id,last_name lastName,email,gender from tbl_employee where id = ? and last_name = ?
==> Parameters: 1(Integer), admin(String)
<== Columns: id, lastName, email, gender
<== Row: 1, admin, jerry@qq.com, 女
<== Total: 1
Employee(id=1, lastName=admin, email=jerry@qq.com, gender=女)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
2.输出参数
2.1使用resultType
resultType:执行SQL得到ResultSet转换的类型,使用类型的完全限定名或别名。注意,如果返回的是集合,那应该设置为集合包含的类型,而不是结合本身。
resultType和resultMap,不能同时使用。
2.1.1简单类型
简单类型:8种java原始类型+String
dao接口:
public int getEmpCount();
mapper.xml文件中对应的sql语句:
<select id="getEmpCount" resultType="int">
select count(*) from tbl_employee
</select>
测试程序及结果:
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
int count = mapper.getEmpCount();
System.out.println(count);
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select count(*) from tbl_employee
==> Parameters:
<== Columns: count(*)
<== Row: 30013
<== Total: 1
30013
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
2.1.2对象类型(POJO)
前面几个例子,一直在用这个,就不再另外举例了。
2.1.3map
SQL的查询结果作为Map 中的key和value;
dao接口:
// 返回一条记录的map:key是列名,值就是对于的值
public Map<String,Object> getEmpByIdReturnMap(Integer id);
mapper.xml文件中对应的sql语句:
<select id="getEmpByIdReturnMap" resultType="java.util.Map">
select * from tbl_employee where id = #{id}
</select>
测试程序及结果:
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Map<String, Object> emp = mapper.getEmpByIdReturnMap(1);
System.out.println(emp);
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select * from tbl_employee where id = ?
==> Parameters: 1(Integer)
<== Columns: id, last_name, email, gender, d_id
<== Row: 1, admin, jerry@qq.com, 女, 1
<== Total: 1
{gender=女, d_id=1, last_name=admin, id=1, email=jerry@qq.com}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
2.2使用resultMap
resultMap可以自定义SQL的结果和java对象属性的映射关系。更灵活的把列值赋值给指定的属性。
使用方式:
- 先定义resultMap,指定列名和属性的对应关系
- 在
<select>
中吧resultType替换成resultMap
1.2.1准备工作
每个员工有与之对应的部门,建立部门表(上面已建立),和改造员工表(加一个字段d_id,表示该员工对应的部门id)
改后的Employee.java,–>EmployeePlus.java
package com.wangsuhang.mybatis_03_mapper.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author suahng
* @date 2021-07-06 9:56
* @dec
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmployeePlus {
private Integer id;
private String lastName;
private String email;
private String gender;
private Department dept;
}
部门表对应的POJO,Department.java
package com.wangsuhang.mybatis_03_mapper.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author suahng
* @date 2021-07-09 19:50
* @dec
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
private Integer id;
private String departmentName;
//该部门下的所有员工信息
private List<EmployeePlus> emps;
}
1.2.2一对一查询
场景:
查询Employee的同时查询员工对应的部门
Employee====>Department
一个员工有与之对应的部门信息
dao接口:
public EmployeePlus getEmpAndDeptById(Integer id);
方式一:级联属性封装结果集
mapper.xml文件中对应的sql语句:
<!-- 方法1:级联属性封装结果集-->
<resultMap id="MyDifEmp" type="com.wangsuhang.mybatis_03_mapper.pojo.EmployeePlus">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender" />
<result column="did" property="dept.id"/>
<result column="dept_name" property="dept.departmentName"/>
</resultMap>
<!-- public Employee getEmpAndDeptById(Integer id); -->
<select id="getEmpAndDeptById" resultMap="MyDifEmp2">
SELECT e.id id,e.last_name last_name,e.email email,e.gender gender,e.d_id d_id,d.id did,d.dept_name dept_name
FROM tbl_employee e JOIN tbl_dept d ON e.d_id = d.id
WHERE e.id = #{id}
</select>
测试程序及结果:
EmployeeMapperPlus mapper = sqlSession.getMapper(EmployeeMapperPlus.class);
EmployeePlus emp = mapper.getEmpAndDeptById(1);
System.out.println(emp);
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: SELECT e.id id,e.last_name last_name,e.email email,e.gender gender,e.d_id d_id,d.id did,d.dept_name dept_name FROM tbl_employee e JOIN tbl_dept d ON e.d_id = d.id WHERE e.id = ?
==> Parameters: 1(Integer)
<== Columns: id, last_name, email, gender, d_id, did, dept_name
<== Row: 1, admin, jerry@qq.com, 女, 1, 1, 开发部
<== Total: 1
EmployeePlus(id=1, lastName=admin, email=jerry@qq.com, gender=女, dept=Department(id=1, departmentName=开发部, emps=null))
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
方法2:使用association定义关联单个对象的规则
mapper.xml文件中对应的sql语句:
<!-- 方法2:使用association定义关联单个对象的规则 -->
<resultMap id="MyDifEmp2" type="com.wangsuhang.mybatis_03_mapper.pojo.EmployeePlus">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender" />
<!-- association可以指定联合的javaBean对象
property="dept":指定哪个属性是联合的对象
JavaType:指定哪个属性对象的类型 [不能省略]
-->
<association property="dept" javaType="com.wangsuhang.mybatis_03_mapper.pojo.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
<!-- public Employee getEmpAndDeptById(Integer id); -->
<select id="getEmpAndDeptById" resultMap="MyDifEmp2">
SELECT e.id id,e.last_name last_name,e.email email,e.gender gender,e.d_id d_id,d.id did,d.dept_name dept_name
FROM tbl_employee e JOIN tbl_dept d ON e.d_id = d.id
WHERE e.id = #{id}
</select>
测试程序及结果同上,就不在写了。
association:表示进行关联查询单条记录
property:表示关联查询的结果存储在com.wangsuhang.mybatis_03_mapper.pojo.EmployeePlus的dep属性中
javaType:表示关联查询的结果类型
<id property="did" column="id"/>
:查询结果的did列对应关联对象的id属性;
<result property="。departmentName" column="dept_name"/>
:查询结果的dept_name列对应关联对象的departmentName属性
1.2.2多对一查询
场景二:
查询部门的时候将部门对应的所有员工信息也查询出来:详细信息在DepartmentMapper.xml中
dao接口:
public Department getDeptByIdPlus(Integer id);
mapper.xml文件中对应的sql语句:
<!--
collection:嵌套结果集的方式,定义关联的集合类型元素的封装规则
-->
<!-- public Department getDeptByIdPlus(Integer id); -->
<resultMap id="MyDept" type="com.wangsuhang.mybatis_03_mapper.pojo.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
<!--
collection:定义关联集合类型的封装规则
property:将要关联的信息映射到哪个属性中
ofType:指定集合里面元素的类型
-->
<collection property="emps" ofType="com.wangsuhang.mybatis_03_mapper.pojo.EmployeePlus">
<!-- 定义这个集合中元素的封装规则 -->
<id column="eid" property="id"/>
<result column="last_name" property="lastName" />
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
<select id="getDeptByIdPlus" resultMap="MyDept">
SELECT d.id did,d.dept_name dept_name,
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=#{did}
</select>
测试程序及结果:
DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
Department dept = mapper.getDeptByIdPlus(1);
System.out.println(dept);
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: SELECT d.id did,d.dept_name dept_name, 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=?
==> Parameters: 1(Integer)
<== Columns: did, dept_name, eid, last_name, email, gender
<== Row: 1, 开发部, 1, admin, jerry@qq.com, 女
<== Row: 1, 开发部, 4, wsh, wsh@qq.com, 男
<== Row: 1, 开发部, 7, smith, smith@qq.com, 男
<== Row: 1, 开发部, 8, allen, allen@qq.com, 男
<== Row: 1, 开发部, 11, smith, smith@qq.com, 男
<== Row: 1, 开发部, 12, allen, allen@qq.com, 男
<== Total: 6
Department(id=1, departmentName=开发部, emps=[EmployeePlus(id=1, lastName=admin, email=jerry@qq.com, gender=女, dept=null), EmployeePlus(id=4, lastName=wsh, email=wsh@qq.com, gender=男, dept=null), EmployeePlus(id=7, lastName=smith, email=smith@qq.com, gender=男, dept=null), EmployeePlus(id=8, lastName=allen, email=allen@qq.com, gender=男, dept=null), EmployeePlus(id=11, lastName=smith, email=smith@qq.com, gender=男, dept=null), EmployeePlus(id=12, lastName=allen, email=allen@qq.com, gender=男, dept=null)])
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
collection:表示关联查询结果集
property=“emps”:关联查询的结果集存储在com.wangsuhang.mybatis_03_mapper.pojo.Department上那个属性。
==ofType="==com.wangsuhang.mybatis_03_mapper.pojo.EmployeePlus":指定关联查询的结果集中的对象类型即List中的对象类型。
1.2.3多对多查询
一对多是多对多的特例。
场景:
查询所有部门信息,及与之相对应的员工信息
dao接口:
public List<Department> getAll();
mapper.xml文件中对应的sql语句:
<!--
collection:嵌套结果集的方式,定义关联的集合类型元素的封装规则
-->
<!-- public Department getDeptByIdPlus(Integer id); -->
<resultMap id="MyDept" type="com.wangsuhang.mybatis_03_mapper.pojo.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
<!--
collection:定义关联集合类型的封装规则
property:将要关联的信息映射到哪个属性中
ofType:指定集合里面元素的类型
-->
<collection property="emps" ofType="com.wangsuhang.mybatis_03_mapper.pojo.EmployeePlus">
<!-- 定义这个集合中元素的封装规则 -->
<id column="eid" property="id"/>
<result column="last_name" property="lastName" />
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
<select id="getDeptByIdPlus" resultMap="MyDept">
SELECT d.id did,d.dept_name dept_name,
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
</select>
测试程序及结果:
DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
List<Department> departmentList = mapper.getAll();
departmentList.forEach(System.out::println);
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: SELECT d.id did,d.dept_name dept_name, 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
==> Parameters:
<== Columns: did, dept_name, eid, last_name, email, gender
<== Row: 1, 开发部, 1, admin, jerry@qq.com, 女
<== Row: 1, 开发部, 4, wsh, wsh@qq.com, 男
<== Row: 1, 开发部, 7, smith, smith@qq.com, 男
<== Row: 1, 开发部, 8, allen, allen@qq.com, 男
<== Row: 1, 开发部, 11, smith, smith@qq.com, 男
<== Row: 1, 开发部, 12, allen, allen@qq.com, 男
<== Row: 2, 测试部, 3, jerry, jerry@qq.com, 男
<== Row: 2, 测试部, 5, hcy, hcy@qq.com, 女
<== Row: 2, 测试部, 9, hcy, hcy@qq.com, 男
<== Row: 2, 测试部, 10, wsh, wsh@qq.com, 男
<== Row: 2, 测试部, 13, hcy, hcy@qq.com, 男
<== Row: 2, 测试部, 14, wsh, wsh@qq.com, 男
<== Total: 12
Department(id=1, departmentName=开发部, emps=[EmployeePlus(id=1, lastName=admin, email=jerry@qq.com, gender=女, dept=null), EmployeePlus(id=4, lastName=wsh, email=wsh@qq.com, gender=男, dept=null), EmployeePlus(id=7, lastName=smith, email=smith@qq.com, gender=男, dept=null), EmployeePlus(id=8, lastName=allen, email=allen@qq.com, gender=男, dept=null), EmployeePlus(id=11, lastName=smith, email=smith@qq.com, gender=男, dept=null), EmployeePlus(id=12, lastName=allen, email=allen@qq.com, gender=男, dept=null)])
Department(id=2, departmentName=测试部, emps=[EmployeePlus(id=3, lastName=jerry, email=jerry@qq.com, gender=男, dept=null), EmployeePlus(id=5, lastName=hcy, email=hcy@qq.com, gender=女, dept=null), EmployeePlus(id=9, lastName=hcy, email=hcy@qq.com, gender=男, dept=null), EmployeePlus(id=10, lastName=wsh, email=wsh@qq.com, gender=男, dept=null), EmployeePlus(id=13, lastName=hcy, email=hcy@qq.com, gender=男, dept=null), EmployeePlus(id=14, lastName=wsh, email=wsh@qq.com, gender=男, dept=null)])
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
小结:
- resultType:将查询结果安装SQL列名及POJO属性名一致性映射到POJO中;
- resultMap:使用association和collection完成一对一和一对多等高级映射(对结果有特殊的映射要求)
- association:将关联查询信息映射到一个pojo对象中
- collection:将关联查询信息映射到一个list集合中
1.2.4分步查询
使用association进行分步查询
1.先按照员工id查询员工信息
2.根据查询员工信息中的d_id值去部门表中查出部门信息
3.部门信息设置到员工中
dao接口:
public EmployeePlus getEmpAndDeptByIdStep(Integer id);
mapper.xml文件中对应的sql语句:
EmployeeMpperPlus.xml文件中相关SQL语句:
<!-- 使用association进行分步查询
1.先按照员工id查询员工信息
2.根据查询员工信息中的d_id值去部门表中查出部门信息
3.部门信息设置到员工中
-->
<resultMap id="MyEmpByStep" type="com.wangsuhang.mybatis_03_mapper.pojo.EmployeePlus">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender" />
<!-- association定义关联对象的封装规则 -->
<!-- association可以指定联合的javaBean对象
select:表明当前属性是调用select指定的方法查出的结果
column:指定将那一列的值传给这个方法
流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并分装给property指定的属性
-->
<association property="dept"
select="com.wangsuhang.mybatis_03_mapper.mapper.DepartmentMapper.getDepartById"
column="d_id">
</association>
</resultMap>
<!-- public EmployeePlus getEmpAndDeptByIdStep(Integer id); -->
<select id="getEmpAndDeptByIdStep" resultMap="MyEmpByStep">
select * from tbl_employee where id = #{id}
</select>
Department.xml文件中相关SQL语句:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wangsuhang.mybatis_03_mapper.mapper.DepartmentMapper">
<!-- public Department getDepartById(Integer id); -->
<select id="getDepartById" resultType="com.wangsuhang.mybatis_03_mapper.pojo.Department">
select id,dept_name departmentName from tbl_dept where id = #{id}
</select>
</mapper>
测试程序及结果:
EmployeeMapperPlus mapper = sqlSession.getMapper(EmployeeMapperPlus.class);
EmployeePlus emp = mapper.getEmpAndDeptByIdStep(1);
System.out.println(emp);
注意SQL语句,是发了2此的:
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select * from tbl_employee where id = ?
==> Parameters: 1(Integer)
<== Columns: id, last_name, email, gender, d_id
<== Row: 1, admin, jerry@qq.com, 女, 1
<== Total: 1
==> Preparing: select id,dept_name departmentName from tbl_dept where id = ?
==> Parameters: 1(Integer)
<== Columns: id, departmentName
<== Row: 1, 开发部
<== Total: 1
EmployeePlus(id=1, lastName=admin, email=jerry@qq.com, gender=女, dept=Department(id=1, departmentName=开发部, emps=null))
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
1.2.5延迟加载(按需加载)
延迟加载:
先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
mybatis的延迟加载默认是关闭的,可以通过全局配置文件中的<setting name="lazyLoadingEnabled" value="true"/>
来开启,开启后,所有的SELECT查询,若有关联对象,都会采用延迟加载的策略。当然,也可以对指定的某个CRUD标签单独禁用延迟加载策略,通过设置SELECT标签中的fetchType=eager,则可以关闭该标签的延迟加载。
(还有一个侵入式延迟加载的概念,在配置文件中通过<setting name="aggressiveLazyLoading" value="true">
来开启,大概是说,访问主对象中的主信息时,就会触发延迟加载,将从信息查询上来,这其实并不是真正意义的延迟加载,真正意义上的延迟加载应该是访问主对象中的从信息时,才触发延迟加载,去加载从信息,侵入式延迟加载默认是关闭的,一般情况下可以不用管他)
注意,延迟加载在关联查询的场景下才有意义。需要配合<resultMap>
标签下的<association>
和<collecction>
标签使用
dao接口:
public Department getDeptByIdStep(Integer id);
全局配置文件中需添加的设置:
<!-- 显示的指定每个我们需要更改的配置的值,即使他是默认的,防止版本更新 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
设置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
lazyLoadingEnabled | 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。 | true or false | false |
aggressiveLazyLoading | 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 | true or false | true |
mapper.xml文件中对应的sql语句:
<resultMap id="MyDeptStep" type="com.wangsuhang.mybatis_03_mapper.pojo.Department">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<collection property="emps"
select="com.wangsuhang.mybatis_03_mapper.mapper.EmployeeMapperPlus.getEmpsByDeptId"
column="{deptId=id}" fetchType="lazy"></collection>
</resultMap>
<!-- public Department getDeptByIdStep(Integer id); -->
<select id="getDeptByIdStep" resultMap="MyDeptStep">
select id,dept_name from tbl_dept where id=#{id}
</select>
<!-- 扩展:多列的值传递过去;
将多列的值封装到map中传递:
column="{key1=column1,key2=column2}"
fetchType="lazy":表示使用延迟加载
lazy:延迟
eager:立即
-->
测试程序及结果:
- 不查询员工信息
DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
Department dept = mapper.getDeptByIdStep(1);
System.out.println(dept.getDepartmentName());
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select id,dept_name from tbl_dept where id=?
==> Parameters: 1(Integer)
<== Columns: id, dept_name
<== Row: 1, 开发部
<== Total: 1
开发部
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
- 查询员工信息
DepartmentMapper mapper = sqlSession.getMapper(DepartmentMapper.class);
Department dept = mapper.getDeptByIdStep(1);
System.out.println(dept.getEmps());
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select id,dept_name from tbl_dept where id=?
==> Parameters: 1(Integer)
<== Columns: id, dept_name
<== Row: 1, 开发部
<== Total: 1
==> Preparing: select * from tbl_employee where d_id=?
==> Parameters: 1(Integer)
<== Columns: id, last_name, email, gender, d_id
<== Row: 1, admin, jerry@qq.com, 女, 1
<== Row: 4, wsh, wsh@qq.com, 男, 1
<== Row: 7, smith, smith@qq.com, 男, 1
<== Row: 8, allen, allen@qq.com, 男, 1
<== Row: 11, smith, smith@qq.com, 男, 1
<== Row: 12, allen, allen@qq.com, 男, 1
<== Total: 6
[Employee(id=1, lastName=admin, email=jerry@qq.com, gender=女), Employee(id=4, lastName=wsh, email=wsh@qq.com, gender=男), Employee(id=7, lastName=smith, email=smith@qq.com, gender=男), Employee(id=8, lastName=allen, email=allen@qq.com, gender=男), Employee(id=11, lastName=smith, email=smith@qq.com, gender=男), Employee(id=12, lastName=allen, email=allen@qq.com, gender=男)]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
3.mapper.xml的SQL语句中的占位${}
和#{}
一般会采用#{},#{}在mybatis中,最后会被解析为?,其实就是Jdbc的PreparedStatement中的?占位符,它有预编译的过程,会对输入参数进行类型解析(如果入参是String类型,设置参数时会自动加上引号),可以防止SQL注入,如果parameterType属性指定的入参类型是简单类型的话(简单类型指的是8种java原始类型再加一个String),#{}中的变量名可以任意,如果入参类型是pojo,比如是Student类
public class Student{
private String name;
private Integer age;
//setter/getter
}
那么#{name}
表示取入参对象Student中的name属性,#{age}
表示取age属性,这个过程是通过反射来做的,这不同于${}
,${}
取对象的属性使用的是OGNL(Object Graph Navigation Language)表达式.
那么#{name}表示取入参对象Student中的name属性,#{age}表示取age属性,这个过程是通过反射来做的,这不同于 , {}, ,{}取对象的属性使用的是OGNL(Object Graph Navigation Language)表达式
而== = = , 一 般 会 用 在 模 糊 查 询 的 情 景 , 比 如 ‘ s e l e c t ∗ f r o m s t u d e n t w h e r e n a m e l i k e ′ {}==,一般会用在模糊查询的情景,比如`select * from student where name like '% ==,一般会用在模糊查询的情景,比如‘select∗fromstudentwherenamelike′{name}%’;`
它的处理阶段在==#{}==之前,它不会做参数类型解析,而仅仅是做了字符串的拼接,若入参的Student对象的name属性为zhangsan,则上面那条SQL最终被解析为SELECT * FROM student WHERE name like '%zhangsan%';
,而如果此时用的是SELECT * FROM student WHERE name like '%#{name}%';
这条SQL最终就会变成
SELECT * FROM student WHERE name like '%'zhangsan'%';
所以模糊查询用${}
,虽然普通的入参也可以用
,
但
‘
{},但`
,但‘{}不会做类型解析,就存在SQL注入的风险,比如
SELECT * FROM user WHERE name = ‘
n
a
m
e
′
A
N
D
p
a
s
s
w
o
r
d
=
′
{name}' AND password = '
name′ANDpassword=′{password}’,我可以让一个user对象的password属性为
'OR ‘1’ = '1,最终的SQL就变成了
SELECT * FROM user WHERE name = ‘yogurt’ AND password = ''OR ‘1’ = '1’,因为
OR ‘1’ = ‘1’`恒成立,这样攻击者在不需要知道用户名和密码的情况下,也能够完成登录验证.
另外,对于pojo的入参,${}
中获取对象属性的语法和#{}
几乎一样,但${}
在mybatis底层是通过OGNL表达式语言进行处理的,这跟#{}
的反射处理有所不同.
对于简单类型(8种java原始类型再加一个String)的入参,${}
中参数的名字必须是value
,例子如下
<select id="fuzzyCount" parameterType="string" resultType="int">
SELECT count(1) FROM `user` WHERE name like '%${value}%'
</select>
为什么简单类型的变量名必须为value呢?因为mybatis源码中写死的value,如下:
五、动态SQL
动态SQL是Mybatis强大特性之一,极大的简化我们拼装SQL的操作
动态SQL元素和使用JSTL或其他类似于XML的文本处理器相似
Mybatis采用功能强大的基于OGNL的表达式来简化操作。
- if:判断
- choose (when, otherwise):分支选择
- trim (where, set):字符串截取(where(封装查询条件),set(封装修改条件)
- foreach:循环
1.if
dao接口:
// 携带了哪个字段查询条件就带上了这个字段的值
public List<EmployeePlus> getEmpsByConditionIf(EmployeePlus employeePlus);
mapper.xml文件中对应的sql语句:
当满足test条件时,才会将<if>
标签内的SQL语句拼接上去
<select id="getEmpsByConditionIf" resultType="com.wangsuhang.mybatis_04_DynamicSQL.pojo.EmployeePlus">
select
<include refid="mysql"></include>
from tbl_employee
where 1=1
<!-- test:判断表达式(OGNL)
从参数中取值进行判断
遇见特殊符号应该写转义字符
&&
""
-->
<if test="id!=null">
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=="男" or gender=="女"">
and gender=#{gender}
</if>
</select>
测试程序及结果:
EmployeeMapperDynamicSQL mapper = sqlSession.getMapper(EmployeeMapperDynamicSQL.class);
EmployeePlus emp = new EmployeePlus();
emp.setLastName("%a%");
emp.setGender("女");
List<EmployeePlus> emps = mapper.getEmpsByConditionIf(emp);
emps.forEach(System.out::println);
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select id,last_name,email from tbl_employee where 1=1 and last_name like ? and gender=?
==> Parameters: %a%(String), 女(String)
<== Columns: id, last_name, email
<== Row: 1, admin, jerry@qq.com
<== Total: 1
EmployeePlus(id=1, lastName=admin, email=jerry@qq.com, gender=null, dept=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
SQL语句:select id,last_name,email from tbl_employee where 1=1 and last_name like ? and gender=?
2.choose (when, otherwise)
dao接口:
public List<EmployeePlus> getEmpsByConditionChoose(EmployeePlus employeePlus);
mapper.xml文件中对应的sql语句:
choose 和 when , otherwise 是配套标签 类似于java中的switch,只会选中满足条件的一个
<!-- public List<EmployeePlus> getEmpsByConditionChoose(EmployeePlus employeePlus); -->
<select id="getEmpsByConditionChoose" resultType="com.wangsuhang.mybatis_04_DynamicSQL.pojo.EmployeePlus">
select * from tbl_employee
<where>
<choose>
<when test="id!=null">
id=#{id}
</when>
<when test="lastName!=null">
last_name like #{lastName}
</when>
<when test="email">
email=#{email}
</when>
<otherwise>
gender = "女"
</otherwise>
</choose>
</where>
</select>
测试程序及结果:
EmployeeMapperDynamicSQL mapper = sqlSession.getMapper(EmployeeMapperDynamicSQL.class);
EmployeePlus emp = new EmployeePlus();
// emp.setLastName("%e%");
emp.setEmail("jerry@qq.com");
List<EmployeePlus> emps = mapper.getEmpsByConditionChoose(emp);
emps.forEach(System.out::println);
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select * from tbl_employee WHERE email=?
==> Parameters: jerry@qq.com(String)
<== Columns: id, last_name, email, gender, d_id
<== Row: 1, admin, jerry@qq.com, 女, 1
<== Row: 3, jerry, jerry@qq.com, 男, 2
<== Total: 2
EmployeePlus(id=1, lastName=admin, email=jerry@qq.com, gender=女, dept=null)
EmployeePlus(id=3, lastName=jerry, email=jerry@qq.com, gender=男, dept=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
SQL语句:select * from tbl_employee WHERE email=?
,java测试程序中emp.setEmail(“jerry@qq.com”);,所以email不为空,若email也为空,则查询条件就会是默认值(<otherwise>
): gender = “女”
3.trim (where, set)
dao接口:
public List<EmployeePlus> getEmpsByConditionTrim(EmployeePlus employeePlus);
mapper.xml文件中对应的sql语句:
<select id="getEmpsByConditionTrim" resultType="com.wangsuhang.mybatis_04_DynamicSQL.pojo.EmployeePlus">
select * from tbl_employee
<!-- 后面多出的and或者or,where标签不能解决 -->
<!--
trim:自定义字符串的截取规则
prefix="":前缀;trim标签中是整个字符串拼串后的结果。prefix给拼串后的整个字符串加一个前缀
prefixOverrides="":前缀覆盖;去掉整个字符串前面多余的字符
suffix="":后缀,suffix给拼串后的整个字符串加一个后缀
suffixOverrides="":后缀覆盖;去掉整个字符串后面多余的字符
-->
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null && lastName!=""">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=""">
email=#{email} and
</if>
<if test="gender=="男" or gender=="女"">
gender=#{gender}
</if>
</trim>
</select>
测试程序及结果:
EmployeeMapperDynamicSQL mapper = sqlSession.getMapper(EmployeeMapperDynamicSQL.class);
EmployeePlus emp = new EmployeePlus();
emp.setLastName("%e%");
emp.setEmail("jerry@qq.com");
List<EmployeePlus> emps = mapper.getEmpsByConditionTrim(emp);
emps.forEach(System.out::println);
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select * from tbl_employee where last_name like ? and email=?
==> Parameters: %e%(String), jerry@qq.com(String)
<== Columns: id, last_name, email, gender, d_id
<== Row: 3, jerry, jerry@qq.com, 男, 2
<== Total: 1
EmployeePlus(id=3, lastName=jerry, email=jerry@qq.com, gender=男, dept=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
SQL语句:select * from tbl_employee where last_name like ? and email=?
where
<where>
标签只会在至少有一个子元素返回了SQL语句时,才会向SQL语句中添加WHERE,并且如果WHERE之后是以AND或OR开头,会自动将其删掉
<where>
标签可以用<trim>
标签代替
<trim prefix="WHERE" prefixOverrides="AND | OR">
...
</trim>
set
在至少有一个子元素返回了SQL语句时,才会向SQL语句中添加SET,并且如果SET之后是以,
开头的话,会自动将其删掉
<set>
标签相当于如下的<trim>
标签
<trim prefix="SET" prefixOverrides=",">
...
</trim>
dao接口:
public void updateEmp(EmployeePlus employeePlus);
mapper.xml文件中对应的sql语句:
<!-- public void updateEmp(EmployeePlus employeePlus); -->
<update id="updateEmp">
update tbl_employee
<set>
<if test="lastName!=null">
last_name=#{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender}
</if>
</set>
where id=#{id}
</update>
测试程序及结果:
EmployeeMapperDynamicSQL mapper = sqlSession.getMapper(EmployeeMapperDynamicSQL.class);
EmployeePlus emp = new EmployeePlus();
emp.setId(1);
emp.setLastName("admin1");
int num = mapper.updateEmp(emp);
System.out.println(num);
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: update tbl_employee SET last_name=? where id=?
==> Parameters: admin1(String), 1(Integer)
<== Updates: 1
1
Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
4.foreach
用来做迭代拼接的,通常会与SQL语句中的=IN=查询条件结合使用,注意,parameterType为List(链表)或者Array(数组),后面再引用时,参数名必须为list或者array。如在foreach标签中,collection属性则为需要迭代的集合,由于入参是个List,所以参数名必须为list;或者使用@Param,指定参数名
提示:你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
dao接口:
public List<EmployeePlus> getEmpsByConditionForeach(@Param("ids") List<Integer> list);
mapper.xml文件中对应的sql语句:
<!-- public List<EmployeePlus> getEmpsByConditionForeach(List<Integer> ids); -->
<select id="getEmpsByConditionForeach" resultType="com.wangsuhang.mybatis_04_DynamicSQL.pojo.EmployeePlus">
select * from tbl_employee where id in
<!--
collention:指定要遍历的集合:
list类型的参数会特殊处理封装在map中,map的key就叫list
item:将当前遍历出的元素赋值给指定的变量
separator:每个元素之间的分隔符
open:遍历出所有结果拼接一个开始的字符
clos:遍历出所有结果拼接一个结束的字符
index:索引。遍历list的时候index就是索引,item就是当前值
遍历map的时候index表示的就是map的key,item就是map的值
#{变量名}就能取出变量的值也就是当前遍历出的元素
-->
<foreach collection="ids" item="item_id" separator="," open="(" close=")">
#{item_id}
</foreach>
</select>
测试程序及结果:
EmployeeMapperDynamicSQL mapper = sqlSession.getMapper(EmployeeMapperDynamicSQL.class);
List<EmployeePlus> emps = mapper.getEmpsByConditionForeach(Arrays.asList(1, 3, 5));
emps.forEach(System.out::println);
com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 com.wangsuhang.mybatis_04_DynamicSQL.test.MyBatisTest,testDynamicSQL4
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Created connection 1333592072.
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: select * from tbl_employee where id in ( ? , ? , ? )
==> Parameters: 1(Integer), 3(Integer), 5(Integer)
<== Columns: id, last_name, email, gender, d_id
<== Row: 1, admin, jerry@qq.com, 女, 1
<== Row: 3, jerry, jerry@qq.com, 男, 2
<== Row: 5, hcy, hcy@qq.com, 女, 2
<== Total: 3
EmployeePlus(id=1, lastName=admin, email=jerry@qq.com, gender=女, dept=null)
EmployeePlus(id=3, lastName=jerry, email=jerry@qq.com, gender=男, dept=null)
EmployeePlus(id=5, lastName=hcy, email=hcy@qq.com, gender=女, dept=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
注:使用foreach,批量插入
dao接口:
public void addEmps(@Param("emps") List<EmployeePlus> emps);
mapper.xml文件中对应的sql语句:
<!-- public void addEmps(@Param("emps") List<EmployeePlus> emps); -->
<!-- MySQL下批量保存:可以foreach遍历 MySQL支持values(),(),()语法-->
<insert id="addEmps">
insert into tbl_employee(last_name,email,gender,d_id)
values
<foreach collection="emps" item="emps" separator=",">
(#{emps.lastName},#{emps.email},#{emps.gender},#{emps.dept.id})
</foreach>
</insert>
测试程序及结果:
EmployeeMapperDynamicSQL mapper = sqlSession.getMapper(EmployeeMapperDynamicSQL.class);
ArrayList<EmployeePlus> emps = new ArrayList<>();
emps.add(new EmployeePlus(null,"smith","smith@qq.com","男",new Department(1)));
emps.add(new EmployeePlus(null,"allen","allen@qq.com","男",new Department(1)));
emps.add(new EmployeePlus(null,"hcy","hcy@qq.com","男",new Department(2)));
emps.add(new EmployeePlus(null,"wsh","wsh@qq.com","男",new Department(2)));
mapper.addEmps(emps);
sqlSession.commit();
Returned connection 1333592072 to pool.
Opening JDBC Connection
Checked out connection 1333592072 from pool.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
==> Preparing: insert into tbl_employee(last_name,email,gender,d_id) values (?,?,?,?) , (?,?,?,?) , (?,?,?,?) , (?,?,?,?)
==> Parameters: smith(String), smith@qq.com(String), 男(String), 1(Integer), allen(String), allen@qq.com(String), 男(String), 1(Integer), hcy(String), hcy@qq.com(String), 男(String), 2(Integer), wsh(String), wsh@qq.com(String), 男(String), 2(Integer)
<== Updates: 4
Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
time=128
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4f7d0008]
Returned connection 1333592072 to pool.
Process finished with exit code 0
SQL语句:insert into tbl_employee(last_name,email,gender,d_id) values (?,?,?,?) , (?,?,?,?) , (?,?,?,?) , (?,?,?,?)
5.sql
可将重复的SQL片段提取出来,然后在需要的地方,使用<include>
标签进行引用
<select id="getEmpById" resultType="employee">
select * from tbl_employee
<include refid="whereClause"/>
</select>
<sql id="whereClause">
<where>
<if test id!=null>
and id=#{id}
</if>
</where>
</sql>
6.bind
mybatis的动态SQL都是用OGNL表达式进行解析的,如果需要创建OGNL表达式以外的变量,可以用bind标签
public List<EmployeePlus> getEmpsTestInnerParamter(EmployeePlus employeePlus);
mapper.xml文件中对应的sql语句:
两个内置参数:
不只是方法传递过来的参数可以被用来判断,取值
mybatis默认还有两个内置参数
_parameter:代表整个参数
单个参数:_parameter就是这个参数
多个参数:参数会被封装为一个map:_parameter就是代表这个map
_databaseId:如果配置了DataBaseIdProvider标签
_databaseId就是代表当前数据库的别名
<!-- public List<EmployeePlus> getEmpsTestInnerParamter(EmployeePlus employeePlus); -->
<select id="getEmpsTestInnerParamter" resultType="com.wangsuhang.mybatis_04_DynamicSQL.pojo.EmployeePlus">
<!-- bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值-->
<bind name="_lastName" value="'%'+lastName+'%'"/>
<if test="_databaseId==mysql">
select * from tbl_employee
<if test="_parameter!=null">
where last_name = #{_lastName}
</if>
</if>
<if test="_databaseId==oracle">
select * from employees
</if>
</select>
这个就不做测试了。
六、缓存
Mybatis包含了一个非常强大的查询缓存条件,他可以非常方便地配置和定制,缓存可以极大地提升查询效率。
Mybatis系统中默认定义了两级缓存,一级缓存和二级缓存。
- 默认情况下,只有一级缓存(SqlSession级别的缓存,也称本地缓存)开启。
- 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
- 为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来定义了二级缓存。
1.一级缓存
==一级缓存(local cache),即本地缓存,==作用域默认为SqlSession。当Session flush 或 close 后,该Session中的所有Cache将被清空。=
本地缓存不能被关闭,但可以调用clearCache()来清空本地缓存,或者改变缓存作用域
在Mybatis3.1之后,可以配置本地缓存的作用域。
一级缓存演示&失效情况
- 同一次会话期间只需要查询过的数据都会保存在当前SqlSession的一个Map中
- key:hashCode+查询的SqlId+编写的SQL查询语句+参数
- 一级缓存失效的四种情况
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两处查询期间执行了任何一次增删改操作
2.二级缓存
二级缓存(second level cache),全局作用域缓存
二级缓存默认不开启,需要手动配置
Mybatis提供二级缓存的接口以及实现,缓存实现要求POJO接口实现Serializable接口(可序列化)
二级缓存在SqlSession关闭或提交之后才会生效
工作机制:
-
一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
-
如果会话关闭,一级缓存中的数据就会被保存到二级缓存中,新的会话查询,就可以参照二级缓存
-
sqlSession == EmployeeMapper ==> Employee
DepartmentMapper==>Department
不同的namespace查出的数据会放在自己对应的缓存中(map
使用:
- 开启全局二级缓存配置:
<setting name="cacheEnabled" value="true"/>
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 显示的指定每个我们需要更改的配置的值,即使他是默认的,防止版本更新 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
-
需要使用二级缓存的映射文件处使用cache配置缓存:
<cache />
<cache >
-
POJO需要实现Serializable接口,即实现序列化接口
缓存相关属性:
缓存相关配置:
- 全局setting的CacheEnable:配置二级缓存开关,一级缓存一直是打开的。
<setting name="cacheEnabled" value="false"/>:关闭缓存(二级缓存关闭,一级缓存一直是可用的)
- select标签的useCache属性:配置这个select是确定是否使用二级缓存 ,一级缓存一直是可使用的。
每个select标签都有useCache="true":
false:不使用缓存(二级缓存不使用,一级缓存依然在使用)
- sql标签的flushCache属性:增删改默认flushCache=true,SQL执行完后,会同时清空一级和二级缓存;查询默认flushCache=false。
每个增删改标签都有:flushCache="true";(一级二级都会被删除)
增删改执行完成后就会清空缓存;
测试:flushCache="true":一级缓存就清空了;二级缓存也会被清除
查询标签:flushCache="false"
如果flushCache=true;每次查询之后都会清空缓存;缓存没有被使用
- sqlSession.clearCache():只是用来清除一级缓存。
sqlSession.clearCache();只是清除当前session的一级缓存;
-
当在某一个作用域(一级缓存Session/二级缓存namespace)进行了 增删改 操作后,默认该作用域下所有select中的缓存将被clear。
-
localCacheScope:本地缓存作用域:(一级缓存SESSION);当前会话的所有数据保存在会话缓存中; STATEMENT:可以禁用一级缓存;
3.原理
一级缓存
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
二级缓存
首先开启mybatis的二级缓存。
sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到二级缓存中。
如果SqlSession3去执行相同 mapper下sql,执行commit提交,清空该 mapper下的二级缓存区域的数据。
sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个UserMapper的二级缓存区域。
UserMapper有一个二级缓存区域(按namespace分) ,其它mapper也有自己的二级缓存区域(按namespace分)。
每一个namespace的mapper都有一个二缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同 的二级缓存区域中。
顺序
七、其他
1.批量操作
- 默认的openSession()方法没有参数,它会创建有如下特性:
- 会开启一个事务(也就是不自动提交)
- 连接对象会从有活动环境配置的数据源实例得到
- 事务隔离级别将会使用取得或数据源的默认设置
- 预处理数据不会被重复使用,也不会批量处理
- openSession方法的 ExecutorType 类型的参数,枚举类型:
- ExecutorType.SIMPLE:这个之前类型不做特殊的事情(这是默认装配的),他未每个语句的执行创建了一个新的预处理语句
- ExecutorType.REUSE:这个执行器类型会复用预语句
- ExecutorType.BATCH:这个执行器会执行所有更新语句
- 批量操作我们是使用Mybatis提供的BatchExecutor进行的,它的底层就是通过jdbc攒SQL的方式进行的,我们可以让他攒够一定数量后发给数据库一次。
@Test
public void testBatch() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//可以执行批量操作的sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
long start = System.currentTimeMillis();
try {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
for (int i = 0; i < 10000; i++) {
employee.setLastName(UUID.randomUUID().toString().substring(0,5));
employee.setEmail("a"+i+"@qq.com");
employee.setGender("男");
Long aLong = mapper.addEmp(employee);
}
sqlSession.commit();
long end = System.currentTimeMillis();
System.out.println("time is : " + (end - start)); //2626 1960
}finally {
sqlSession.close();
}
}
- 与Spring整合时,推荐,额外的配置一个可以专门用来执行批量操作的sqlSession
需要用到批量操作的时候,我们可以注入配置的这个批量SqlSession。通过他获取到mapper映射器进行操作。