MyBatis 学习笔记

MyBatis

MyBatis官方中文文档

官网

中文文档

MyBatis学习笔记

Hello World

  1. 创建全局测试文件

官方文档提供格式, 下载主配置文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
<mappers>
<mapper resource="EmployeeMapper.xml" />
</mappers>
</configuration>
  1. SQL映射文件

    EmployeeMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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.atguigu.mybatis.dao.EmployeeMapper">
<!--
namespace:名称空间;指定为接口的全类名
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值

public Employee getEmpById(Integer id);
-->
<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
</mapper>
  1. 将sql映射文件注册在全局配置文件中

    1
    2
    3
    4
    <!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
    <mappers>
    <mapper resource="EmployeeMapper.xml" />
    </mappers>

测试使用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}


/**
* 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 有数据源一些运行环境信息
* 2、sql映射文件;配置了每一个sql,以及sql的封装规则等。
* 3、将sql映射文件注册在全局配置文件中
* 4、写代码:
* 1)、根据全局配置文件得到SqlSessionFactory;
* 2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
* 一个sqlSession就是代表和数据库的一次会话,用完关闭
* 3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
*
* @throws IOException
*/
@Test
public void test() throws IOException {

// 2、获取sqlSession实例,能直接执行已经映射的sql语句
// sql的唯一标识:statement Unique identifier matching the statement to use.
// 执行sql要用的参数:parameter A parameter object to pass to the statement.
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

SqlSession openSession = sqlSessionFactory.openSession();
try {
Employee employee = openSession.selectOne(
"com.atguigu.mybatis.EmployeeMapper.selectEmp", 1);
System.out.println(employee);
} finally {
openSession.close();
}

}

接口式编程

namespace: 名称空间指定为接口的全类名

1
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">

使用接口但是不写实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}

@Test
public void test01() throws IOException {
// 1、获取sqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、获取sqlSession对象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、获取接口的实现类对象
//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(1);
System.out.println(mapper.getClass());
System.out.println(employee);
} finally {
openSession.close();
}

}

mybatis会自动生成代理对象执行EmployeeMapper.xml 中定义的增删改查方法,

只需要写一个接口:

1
2
3
4
5
public interface EmployeeMapper {

public Employee getEmpById(Integer id);

}
  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文件

1
2
3
4
5
6
7
driver = com.mysql.jdbc.Driver

url = jdbc:mysql://localhost:3306/mybatis

username = root

password = 333

更改原来的xml配置文件

1
2
3
4
5
6
7
8
9
10
11
<environments default="development">
<environment id="development">
<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>

实际在和spring整合的时候, 这一块配置都交给spring来做了

settings标签

1
2
3
<settings>
<setting/>
</settings>

设置驼峰命名法

​ 这样数据库如果column名字是last_name, User类里的字段是lastName, 那么就依然可以匹配

1
<setting name = "mapUnderscoreToCamelCase" value="true"/>

typeAliases标签

​ 可以为bean类起别名

别名不区分大小写

1
2
3
<typeAliases>
<typeAlias type="com.atguigu.mybatis.bean.Employee" alias="emp"></typeAlias>
</typeAliases>

批量起别名

package: 为某个包下的所有类批量起别名

  • name: 指定包名(此包下和所有子包下所有类, 默认别名是类名小写)

    1
    <package name="com.atguigu.mybatis.bean"/>

使用@Alias注解为某个类

typeHandlers标签

时间类型处理器

自定义处理器

plugins标签

简单介绍:

  • 可以允许插件拦截四大对象

  • 四大对象:

    • Executor
    • ParameterHandler
    • ResultSetHandler
    • StatementHandler

environments标签

environments: 环境们, 配置多重环境, default指定使用某种环境, 可以达到快速切换的目的

environment: 配置具体环境信息

  • 标签下必须含有两个标签
  • transactionManager
    • 事务管理器的类型, JDBC|MANAGED
    • 自定义事务管理器: 实现TransactionFactory接口.type 指定为全类名
  • dataSource:
    • 数据源类型: [UNPOOLED|POOLED|JNDI]
1
2
3
4
5
6
<environments default="test">
<environment id="test">
<transactionManager type="JDBC"></transactionManager>
<dataSource type=""></dataSource>
</environment>
</environments>

​ 可以通过实现DataSourceFactory来使用第三方数据源

1
2
3
4
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}

databaseIdProvider标签

数据库厂商标识

1
<databaseIdProvider type="DB_VENDOR" />

databaseIdProvider 对应的 DB_VENDOR 实现会将 databaseId 设置为 DatabaseMetaData#getDatabaseProductName() 返回的字符串。

由于通常情况下这些字符串都非常长,而且相同产品的不同版本会返回不同的值,你可能想通过设置属性别名来使其变短:

1
2
3
4
5
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>

会将 databaseId 设置为数据库产品名与属性中的名称第一个相匹配的值,如果没有匹配的属性,将会设置为 “null”

可以通过DatabaseIdProvider接口实现自定义标识

1
2
3
4
5
6
public interface DatabaseIdProvider {
default void setProperties(Properties p) { // 从 3.5.2 开始,该方法为默认方法
// 空实现
}
String getDatabaseId(DataSource dataSource) throws SQLException;
}

mappers标签

  • resource
1
2
3
4
5
6
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
  • url 加上file:///完全限定资源定位符
1
2
3
4
5
6
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
  • class: 引用接口
1
2
3
4
5
6
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

​ 全类名和接口名需要一致

也可以直接使用@Select, @Update, @Delete ….直接注解在接口上,

1
2
@Select("select * from tbl_emp where id = #{id}")
public Employee getEmpById(Integer id);
  • name: 批量注册
1
2
3
4
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>

映射文件

增删改查

修改Mapper接口添加增删改查

1
2
3
4
5
6
7
public Employee getEmpById(Integer id);

public Long addEmp(Employee employee);

public boolean updateEmp(Employee employee);

public void deleteEmpById(Integer id);

修改xml映射配置文件编写sql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<!-- public void addEmp(Employee employee); -->
<!-- parameterType:参数类型,可以省略,
获取自增主键的值:
mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys();
useGeneratedKeys="true";使用自增主键获取主键值策略
keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
-->
<insert id="addEmp" parameterType="com.atguigu.mybatis.bean.Employee"
useGeneratedKeys="true" keyProperty="id" databaseId="mysql">
insert into tbl_employee(last_name,email,gender)
values(#{lastName},#{email},#{gender})
</insert>

<!--
获取非自增主键的值:
Oracle不支持自增;Oracle使用序列来模拟自增;
每次插入的数据的主键是从序列中拿到的值;如何获取到这个值;
-->
<insert id="addEmp" databaseId="oracle">
<!--
keyProperty:查出的主键值封装给javaBean的哪个属性
order="BEFORE":当前sql在插入sql之前运行
AFTER:当前sql在插入sql之后运行
resultType:查出的数据的返回值类型

BEFORE运行顺序:
先运行selectKey查询id的sql;查出id值封装给javaBean的id属性
在运行插入的sql;就可以取出id属性对应的值
AFTER运行顺序:
先运行插入的sql(从序列中取出新值作为id);
再运行selectKey查询id的sql;
-->
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
<!-- 编写查询主键的sql语句 -->
<!-- BEFORE-->
select EMPLOYEES_SEQ.nextval from dual
<!-- AFTER:
select EMPLOYEES_SEQ.currval from dual -->
</selectKey>

<!-- 插入时的主键是从序列中拿到的 -->
<!-- BEFORE:-->
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
values(#{id},#{lastName},#{email<!-- ,jdbcType=NULL -->})
<!-- AFTER:
insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL)
values(employees_seq.nextval,#{lastName},#{email}) -->
</insert>

<!-- public void updateEmp(Employee employee); -->
<update id="updateEmp">
update tbl_employee
set last_name=#{lastName},email=#{email},gender=#{gender}
where id=#{id}
</update>

<!-- public void deleteEmpById(Integer id); -->
<delete id="deleteEmpById">
delete from tbl_employee where id=#{id}
</delete>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 测试增删改
* 1、mybatis允许增删改直接定义以下类型返回值
* Integer、Long、Boolean、void
* 2、我们需要手动提交数据
* sqlSessionFactory.openSession();===》手动提交
* sqlSessionFactory.openSession(true);===》自动提交
* @throws IOException
*/
@Test
public void test03() throws IOException{

SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//1、获取到的SqlSession不会自动提交数据
SqlSession openSession = sqlSessionFactory.openSession();

try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
//测试添加
Employee employee = new Employee(null, "jerry4",null, "1");
mapper.addEmp(employee);
System.out.println(employee.getId());

//测试修改
//Employee employee = new Employee(1, "Tom", "jerry@atguigu.com", "0");
//boolean updateEmp = mapper.updateEmp(employee);
//System.out.println(updateEmp);
//测试删除
//mapper.deleteEmpById(2);
//2、手动提交数据
openSession.commit();
}finally{
openSession.close();
}

}
  • 单个参数:mybatis不会做特殊处理,

    • #{参数名/任意名}:取出参数值。
  • 多个参数:mybatis会做特殊处理。

    • 多个参数会被封装成 一个map,

      • key:param1…paramN, 或者参数的索引也可以
      • value:传入的参数值
        • #{}就是从map中获取指定的key的值;
    • ```
      异常:

      org.apache.ibatis.binding.BindingException: 
      Parameter 'id' not found. 
      Available parameters are [1, 0, param1, param2]
      

      操作:

      方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
      取值:#{id},#{lastName}
      
      1
      2
      3
      4
      5
      6
      7



      【命名参数】:明确指定封装参数时map的key;@Param("id")

      ```JAVA
      public Employee getEmpByIdAndLastName(@Param("id")Integer id,@Param("lastName")String lastName);
  • 多个参数会被封装成 一个map,

    • key:使用@Param注解指定的值
    • value:参数值
      • #{指定的key}取出对应的参数值
1
2
3
4
<!--  public Employee getEmpByIdAndLastName(Integer id,String lastName);-->
<select id="getEmpByIdAndLastName" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>
  • POJO:如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;

    • #{属性名}:取出传入的pojo的属性值
  • Map:如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map

    • ```JAVA
      public Employee getEmpByMap(Map<String, Object> map);

      1
      2
      3
      4
      5
      6

      - ```JAVA
      Map<String, Object> map = new HashMap<>();
      map.put("id", 2);
      map.put("lastName", "Tom");
      map.put("tableName", "tbl_employee");
    • #{key}:取出map中对应的值

    • ```XML

       <!-- public Employee getEmpByMap(Map<String, Object> map); -->
       <select id="getEmpByMap" resultType="com.atguigu.mybatis.bean.Employee">
           select * from ${tableName} where id=${id} and last_name=#{lastName}
       </select>     <!-- public Employee getEmpByMap(Map<String, Object> map); -->
       <select id="getEmpByMap" resultType="com.atguigu.mybatis.bean.Employee">
           select * from ${tableName} where id=${id} and last_name=#{lastName}
       </select>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15



      - TO:如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象
      Page{
      int index;
      int size;
      }

      --------------

      ## 思考

      ```java
      public Employee getEmp(@Param("id")Integer id,String lastName);
  • 取值:id==>#{id/param1} lastName==>#{param2}

1
public Employee getEmp(Integer id,@Param("e")Employee emp);
  • 取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}
1
public Employee getEmpById(List<Integer> ids);
  • 取值:取出第一个id的值: #{list[0]}

特别注意:如果是Collection(List、Set)类型或者是数组,也会特殊处理。也是把传入的list或者数组封装在map中。

  • key:
    • Collection(collection),
    • 如果是List还可以使用这个key(list),
    • 数组(array)

结合源码—mybatis怎么处理参数

总结:参数多时会封装map,为了不混乱,我们可以使用@Param来指定封装时使用的key;
#{key}就可以取出map中的值;

(@Param(“id”)Integer id,@Param(“lastName”)String lastName);
ParamNameResolver解析参数封装map的;
//1、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}

args【1,”Tom”,’hello’】:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
//1、参数为null直接返回
if (args == null || paramCount == 0) {
return null;

//2、如果只有一个元素,并且没有Param注解;args[0]:单个参数直接返回
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];

//3、多个元素或者有Param标注
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;

//4、遍历names集合;{0=id, 1=lastName,2=2}
for (Map.Entry<Integer, String> entry : names.entrySet()) {

//names集合的value作为key; names集合的key又作为取值的参考args[0]:args【1,"Tom"】:
//eg:{id=args[0]:1,lastName=args[1]:Tom,2=args[2]}
param.put(entry.getValue(), args[entry.getKey()]);

// add generic param names (param1, param2, ...)param
//额外的将每一个参数也保存到map中,使用新的key:param1...paramN
//效果:有Param注解可以#{指定的key},或者#{param1}
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中的值或者pojo对象属性的值;
${}:可以获取map中的值或者pojo对象属性的值;

1
select * from tbl_employee where id=${id} and last_name=#{lastName}
1
Preparing: select * from tbl_employee where id=2 and last_name=?
  • 区别:
    • #{}:是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
    • ${}:取出的值直接拼装在sql语句中;会有安全问题;
          大多情况下,我们去参数的值都应该去使用#{};
      

原生jdbc不支持占位符的地方我们就可以使用${}进行取值
比如分表、排序。。。;按照年份分表拆分

1
2
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}

#{}:更丰富的用法:

​ 规定参数的一些规则:
​ javaType、 jdbcType、 mode(存储过程)、 numericScale、
​ resultMap、 typeHandler、 jdbcTypeName、 expression(未来准备支持的功能);

  • jdbcType通常需要在某种特定的条件下被设置:
    • 在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错);
  • JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能正确处理;

由于全局配置中:jdbcTypeForNull=OTHER;

oracle不支持;两种办法:

  1. #{email,jdbcType=OTHER}; 赋值的时候指定值
  2. jdbcTypeForNull=NULL
1
<setting name="jdbcTypeForNull" value="NULL"/>

自动映射

全局setting设置

  • autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean属性名一致
  • 如果autoMappingBehavior设置为null则会取消自动映射
  • 数据库字段命名规范,POJO属性符合驼峰命名法,如A_COLUMNaColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true。

自定义resultMap,实现高级结果集映射。

resultMap

和resultType只能二选一

可以使用rusultMap自定义映射规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MySimpleEmp">
<!--指定主键列的封装规则
id定义主键会底层有优化;
column:指定哪一列
property:指定对应的javaBean属性
-->
<id column="id" property="id"/>
<!-- 定义普通列封装规则 -->
<result column="last_name" property="lastName"/>
<!-- 其他不指定的列会自动封装:我们只要写resultMap就把全部的映射规则都写上。 -->
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>

<!-- resultMap:自定义结果集映射规则; -->
<!-- public Employee getEmpById(Integer id); -->
<select id="getEmpById" resultMap="MySimpleEmp">
select * from tbl_employee where id=#{id}
</select>
  • constructor-类在实例化时, 用来注入结果到构造方法中
    • idArg-ID 参数; 标记结果作为ID 可以帮助提高整体效能
    • arg-注入到构造方法的一个普通结果
  • id––一个ID 结果; 标记结果作为ID 可以帮助提高整体效能
  • result–注入到字段或JavaBean 属性的普通结果
  • association–一个复杂的类型关联;许多结果将包成这种类型
    • 嵌入结果映射–结果映射自身的关联,或者参考一个
  • collection–复杂类型的集
    • 嵌入结果映射–结果映射自身的集,或者参考一个
  • discriminator–使用结果值来决定使用哪个结果映射
    • case–基于某些值的结果映射
    • 嵌入结果映射–这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。

场景一:

查询Employee的同时查询员工对应的部门
Employee===Department
一个员工有与之对应的部门信息;

1
id	last_name	gender	d_id	did  dept_name (private Department dept;)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<!--
联合查询:级联属性封装结果集
-->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp">
<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 getEmpAndDept(Integer id);-->
<select id="getEmpAndDept" resultMap="MyDifEmp">
SELECT e.id id,e.last_name last_name,
e.gender gender,e.d_id d_id,
d.id did,d.dept_name dept_name
FROM tbl_employee e,tbl_dept d
WHERE e.d_id=d.id AND e.id=#{id}
</select>

因为JavaBean Employee里面有个人dept属性, 所以级联查询直接引用属性的属性—-dept.id

1
2
3
4
5
6
7
8
@Alias("emp")
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
private Department dept;
}

Department:

1
2
3
4
5
6
public class Department {

private Integer id;
private String departmentName;
private List<Employee> emps;
}

association标签

联级查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 
使用association定义关联的单个对象的封装规则;
-->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp2">
<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.atguigu.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
<!-- public Employee getEmpAndDept(Integer id);-->
<select id="getEmpAndDept" resultMap="MyDifEmp">
SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id,
d.id did,d.dept_name dept_name FROM tbl_employee e,tbl_dept d
WHERE e.d_id=d.id AND e.id=#{id}
</select>

使用association分步查询:

  • 使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!-- 使用association进行分步查询:
1、先按照员工id查询员工信息
2、根据查询员工信息中的d_id值去部门表查出部门信息
3、部门设置到员工中;
-->

<!-- id last_name email gender d_id -->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpByStep">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!-- association定义关联对象的封装规则
select:表明当前属性是调用select指定的方法查出的结果
column:指定将哪一列的值传给这个方法

流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性
-->
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</resultMap>
<!-- public Employee getEmpByIdStep(Integer id);-->
<select id="getEmpByIdStep" resultMap="MyEmpByStep">
select * from tbl_employee where id=#{id}
<if test="_parameter!=null">
and 1=1
</if>
</select>

可以使用延迟加载(懒加载);(按需加载)

  • Employee==>Dept:

  • 我们每次查询Employee对象的时候,都将一起查询出来。

  • 部门信息在我们使用的时候再去查询

  • 分段查询的基础之上加上两个配置:

1
2
3
<!--显示的指定每个我们需要更改的配置的值,即使他是默认的。防止版本更新带来的问题  -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>

场景二:Collection 标签

​ 查询部门的时候将部门对应的所有员工信息也查询出来:注释在DepartmentMapper.xml中

Department:

1
2
3
4
5
6
public class Department {
private Integer id;
private String departmentName;
private List<Employee> emps;
//...
}

需要的结果:

1
did  dept_name  ||  eid  last_name  email   gender
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!--嵌套结果集的方式,使用collection标签定义关联的集合类型的属性封装规则  -->
<resultMap type="com.atguigu.mybatis.bean.Department" id="MyDept">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
<!--
collection定义关联集合类型的属性的封装规则
ofType:指定集合里面元素的类型
-->
<collection property="emps" ofType="com.atguigu.mybatis.bean.Employee">
<!-- 定义这个集合中元素的封装规则 -->
<id column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
<!-- public Department getDeptByIdPlus(Integer id); -->
<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=#{id}
</select>

使用collection的分段查询

在EmployeeMapper.xml中

1
2
3
<select id="getEmpsByDeptId" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where d_id=#{deptId}
</select>

然后再DepartmentMapper中使用EmployeeMapper中的getEmpsByDeptId

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- collection:分段查询 -->
<resultMap type="com.atguigu.mybatis.bean.Department" id="MyDeptStep">
<id column="id" property="id"/>
<id column="dept_name" property="departmentName"/>
<collection property="emps"
select="com.atguigu.mybatis.dao.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传递;

    1
    column="{key1=column1,key2=column2}"
  • fetchType=”lazy”:用fetchType作为Collection标签属性表示使用延迟加载;

    • lazy:延迟
    • eager:立即
    • 默认为延迟加载 lazy

discriminator鉴别器

1
<discriminator javaType=""></discriminator>

鉴别器:mybatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为

场景:

如果查出的是女生:就把部门信息查询出来,否则不查询;

如果是男生,把last_name这一列的值赋值给email;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpDis">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!--
column:指定判定的列名
javaType:列值对应的java类型 -->
<discriminator javaType="string" column="gender">
<!--女生 resultType:指定封装的结果类型;不能缺少。/resultMap-->
<case value="0" resultType="com.atguigu.mybatis.bean.Employee">
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</case>
<!--男生 ;如果是男生,把last_name这一列的值赋值给email; -->
<case value="1" resultType="com.atguigu.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="last_name" property="email"/>
<result column="gender" property="gender"/>
</case>
</discriminator>
</resultMap>

id & result

  • id 和result 映射一个单独列的值到简单数据类型(字符串,整型,双精度浮点数,日期等)的属性或字段。

动态SQL

四种标签

  • if:判断

  • choose (when, otherwise):分支选择;带了break的swtich-case

    如果带了id就用id查,如果带了lastName就用lastName查;只会进入其中一个

  • trim 字符串截取(where(封装查询条件), set(封装修改条件))

  • foreach 遍历集合

if

创建一个新的映射

1
2
3
4
public interface EmployeeMapperDynamic {
public List<Employee> getEmpsByConditionIf(Employee employee);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- 查询员工,要求,携带了哪个字段查询条件就带上这个字段的值 -->
<!-- public List<Employee> getEmpsByConditionIf(Employee employee); -->
<select id="getEmpsByConditionIf" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
<!-- where -->
<where>
<!-- test:判断表达式(OGNL)
OGNL参照PPT或者官方文档。
c:if test
从参数中取值进行判断

遇见特殊符号应该去写转义字符:
&&:
-->
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
and last_name like #{lastName}
</if>
<if test="email!=null and email.trim()!=&quot;&quot;">
and email=#{email}
</if>
<!-- ognl会进行字符串与数字的转换判断 "0"==0 -->
<if test="gender==0 or gender==1">
and gender=#{gender}
</if>
</where>
</select>

查询的时候如果某些条件没带可能sql拼装会有问题

  1. 给where后面加上1=1,以后的条件都and xxx.
  2. mybatis使用where标签来将所有的查询条件包括在内。mybatis就会将where标签中拼装的sql,多出来的and或者or去掉

但是where只会去掉第一个多出来的and或者or。

这时候可以使用trim标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!--public List<Employee> getEmpsByConditionTrim(Employee employee);  -->
<select id="getEmpsByConditionTrim" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee
<!-- 后面多出的and或者or where标签不能解决
prefix="":前缀:trim标签体中是整个字符串拼串 后的结果。
prefix给拼串后的整个字符串加一个前缀
prefixOverrides="":
前缀覆盖: 去掉整个字符串前面多余的字符
suffix="":后缀
suffix给拼串后的整个字符串加一个后缀
suffixOverrides=""
后缀覆盖:去掉整个字符串后面多余的字符

-->
<!-- 自定义字符串的截取规则 -->
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=&quot;&quot;">
email=#{email} and
</if>
<!-- ognl会进行字符串与数字的转换判断 "0"==0 -->
<if test="gender==0 or gender==1">
gender=#{gender}
</if>
</trim>
</select>

choose

如果带了id就用id查,如果带了lastName就用lastName查;只会进入其中一个

1
public List<Employee> getEmpsByConditionChoose(Employee employee);
1
2
3
4
5
//测试choose
/*List<Employee> list = mapper.getEmpsByConditionChoose(employee);
for (Employee emp : list) {
System.out.println(emp);
}*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- public List<Employee> getEmpsByConditionChoose(Employee employee); -->
<select id="getEmpsByConditionChoose" resultType="com.atguigu.mybatis.bean.Employee">
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!=null">
email = #{email}
</when>
<otherwise>
gender = 0
</otherwise>
</choose>
</where>
</select>

set标签

可以取出多余的逗号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--public void updateEmp(Employee employee);  -->
<update id="updateEmp">
<!-- Set标签的使用 -->
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>

trim

可以做到跟set一样的效果, 去除多余的逗号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<update id="updateEmp">	
Trim:更新拼串
update tbl_employee
<trim prefix="set" suffixOverrides=",">
<if test="lastName!=null">
last_name=#{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender}
</if>
</trim>
where id=#{id}
</update>

foreach

  • collection:指定要遍历的集合:

    • list类型的参数会特殊处理封装在map中,map的key就叫list
  • item:将当前遍历出的元素赋值给指定的变量

  • separator:每个元素之间的分隔符

  • open:遍历出所有结果拼接一个开始的字符

  • close:遍历出所有结果拼接一个结束的字符

  • index:索引。遍历list的时候是index就是索引,item就是当前值

    • 遍历map的时候index表示的就是map的key,item就是map的值

#{变量名}就能取出变量的值也就是当前遍历出的元素

1
2
3
4
5
6
7
8
9
<!--public List<Employee> getEmpsByConditionForeach(List<Integer> ids);  -->
<select id="getEmpsByConditionForeach" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee

<foreach collection="ids" item="item_id" separator=","
open="where id in(" close=")">
#{item_id}
</foreach>
</select>

测试

1
2
3
4
List<Employee> list = mapper.getEmpsByConditionForeach(Arrays.asList(1,2));
for (Employee emp : list) {
System.out.println(emp);
}

批量保存

1
2
3
4
5
6
7
8
9
10
11
<!--public void addEmps(@Param("emps")List<Employee> emps);  -->
<!--MySQL下批量保存:可以foreach遍历 mysql支持values(),(),()语法-->
<insert id="addEmps">
insert into tbl_employee(
<include refid="insertColumn"></include>
)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
</foreach>
</insert><!-- -->

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testBatchSave() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try{
EmployeeMapperDynamicSQL mapper = openSession.getMapper(EmployeeMapperDynamicSQL.class);
List<Employee> emps = new ArrayList<>();
emps.add(new Employee(null, "smith0x1", "smith0x1@atguigu.com", "1",new Department(1)));
emps.add(new Employee(null, "allen0x1", "allen0x1@atguigu.com", "0",new Department(1)));
mapper.addEmps(emps);
openSession.commit();
}finally{
openSession.close();
}
}

第二种方式:

1
2
3
4
5
6
7
8
 <!-- 这种方式需要数据库连接属性allowMultiQueries=true;
这种分号分隔多个sql可以用于其他的批量操作(删除,修改) -->
<insert id="addEmps">
<foreach collection="emps" item="emp" separator=";">
insert into tbl_employee(last_name,email,gender,d_id)
values(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
</foreach>
</insert>

但是这种方式需要开启jdbc连接属性的分号隔开功能 — allowMultiQueries

1
jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true

Oracle中的批量保存

  • Oracle不支持values(),(),()

         Oracle支持的批量方式
    
    1. 多个insert放在begin - end里面

      1
      2
      3
      4
      5
      6
      begin
      insert into employees(employee_id,last_name,email)
      values(employees_seq.nextval,'test_001','test_001@atguigu.com');
      insert into employees(employee_id,last_name,email)
      values(employees_seq.nextval,'test_002','test_002@atguigu.com');
      end;
    2. 利用中间表:

      1
      2
      3
      4
      5
      6
      7
      insert into employees(employee_id,last_name,email)
      select employees_seq.nextval,lastName,email from(
      union
      select 'test_a_02' lastName,'test_a_e02' email from dual
      union
      select 'test_a_03' lastName,'test_a_e03' email from dual
      )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<insert id="addEmps" databaseId="oracle">
<!-- 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> -->

<!-- oracle第二种批量方式 -->
insert into employees(
<!-- 引用外部定义的sql -->
<include refid="insertColumn">
<property name="testColomn" value="abc"/>
</include>
)
<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内置参数

不只是方法传递过来的参数可以被用来判断,取值。。。

mybatis默认还有两个内置参数:

  • parameter:代表整个参数

    • _单个参数:_parameter就是这个参数
    • 多个参数:参数会被封装为一个map;_parameter就是代表这个map
  • databaseId:如果配置了databaseIdProvider标签。

    • databaseId就是代表当前数据库的别名oracle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--public List<Employee> getEmpsTestInnerParameter(Employee employee);  -->
<select id="getEmpsTestInnerParameter" resultType="com.atguigu.mybatis.bean.Employee">
<!-- bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值 -->
<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>

bind

bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--public List<Employee> getEmpsTestInnerParameter(Employee employee);  -->
<select id="getEmpsTestInnerParameter" resultType="com.atguigu.mybatis.bean.Employee">
<!-- bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值 -->
<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片段。方便后面引用

  1. sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用

  2. include来引用已经抽取的sql:

  3. include还可以自定义一些property,sql标签内部就能使用自定义的属性

    1. include-property:取值的正确方式${prop},
    2. #{不能使用这种方式}
1
2
3
4
5
6
7
8
9
<insert id="addEmps">
insert into tbl_employee(
<include refid="insertColumn"></include>
)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
</foreach>
</insert>

引用抽取出来的insertColumn

1
2
3
4
5
6
7
8
<sql id="insertColumn">
<if test="_databaseId=='oracle'">
employee_id,last_name,email
</if>
<if test="_databaseId=='mysql'">
last_name,email,gender,d_id
</if>
</sql>

缓存

  • MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存一级缓存

缓存的顺序:

  1. 二级缓存
  2. 一级缓存

一级缓存

sqlSession级别的缓存。一级缓存是一直开启的;SqlSession级别的一个Map

也称为本地缓存, 与数据库同一次会话查询到的数据会放在本地缓存中

以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;

一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询):

  • sqlSession不同。
    
  • sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)
    
  • sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
    
  • sqlSession相同,手动清除了一级缓存(缓存清空)
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test
public void testFirstLevelCache() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01); //查两次只会发一次sql


//xxxxx
//1、sqlSession不同。
//SqlSession openSession2 = sqlSessionFactory.openSession();
//EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);

//2、sqlSession相同,查询条件不同

//3、sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
//mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
//System.out.println("数据添加成功");

//4、sqlSession相同,手动清除了一级缓存(缓存清空)
//openSession.clearCache();

Employee emp02 = mapper.getEmpById(1);
//Employee emp03 = mapper.getEmpById(3);
System.out.println(emp02);
//System.out.println(emp03);
System.out.println(emp01==emp02);

//openSession2.close();
}finally{
openSession.close();
}
}

二级缓存

(全局缓存):基于namespace级别的缓存:一个namespace对应一个二级缓存:

工作机制:

  1. 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;

  2. 如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;

  3. 不同namespace查出的数据会放在自己对应的缓存中(map)

    1. sqlSession===EmployeeMapper==>Employee
    2. DepartmentMapper===>Department

使用:

  1. 在全局配置文件中开启全局二级缓存配置:

    1
    <setting name="cacheEnabled" value="true"/>

    去各个mapper.xml中配置使用二级缓存:不写标签属性就代表都是默认值

1
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
  1. 我们的POJO需要实现序列化接口

和缓存有关的设置/属性:

  1. cacheEnabled=true:false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)
  2. 每个select标签都有useCache=”true”:
    • false:不使用缓存(一级缓存依然使用,二级缓存不使用)
  3. 每个增删改标签的:flushCache=”true”:(一级二级都会清除)
    • 增删改执行完成后就会清楚缓存;
    • 测试:flushCache=”true”:一级缓存就清空了;二级也会被清除;
    • 查询标签:flushCache=”false”:
      • 如果flushCache=true;每次查询之后都会清空缓存;缓存是没有被使用的;
  4. sqlSession.clearCache();只是清楚当前session的一级缓存;
  5. localCacheScope:本地缓存作用域:(一级缓存SESSION);当前会话的所有数据保存在会缓存中;
    • STATEMENT:可以禁用一级缓存;

cache 标签

在那个命名空间下写, 哪个空间就有二级缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<cache></cache>
<!-- <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> -->
<!--
eviction:缓存的回收策略:
• LRU – 最近最少使用的:移除最长时间不被使用的对象。
• FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
• SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
• WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
• 默认的是 LRU。
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
false:非只读:mybatis觉得获取的数据可能会被修改。
mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
type="":指定自定义缓存的全类名;
实现Cache接口即可;
-->

测试:

效果:数据会从二级缓存中获取

  • 查出的数据都会被默认先放在一级缓存中。
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void testSecondLevelCache() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
SqlSession openSession2 = sqlSessionFactory.openSession();
try{
//1、
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);

Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
openSession.close();

//第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
//mapper2.addEmp(new Employee(null, "aaa", "nnn", "0"));
Employee emp02 = mapper2.getEmpById(1);
System.out.println(emp02);
openSession2.close();

}finally{

}
}

第三方缓存整合:

提供了接口Cache

  1. 导入第三方缓存jar包或者依赖;

  2. 导入MyBatis提供的与第三方缓存整合的适配包;官方有;也可以用适配包依赖

  3. mapper.xml中使用自定义缓存

    1
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
  4. ehcache.xml

    1. ```xml
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101



      # 整合Spring

      先引入各种依赖

      ```xml
      <dependencies>
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.7</version>
      </dependency>
      <dependency>
      <groupId>com.oracle.database.jdbc</groupId>
      <artifactId>ojdbc6</artifactId>
      <version>11.2.0.4</version>
      </dependency>
      <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
      </dependency>
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.26</version>
      </dependency>
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.1</version>
      <scope>compile</scope>
      </dependency>
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.6</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.3.8</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.3.8</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.8</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.8</version>
      </dependency>
      <dependency>
      <groupId>org.bitbucket.swattu</groupId>
      <artifactId>spring-mvc</artifactId>
      <version>4.1.22</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.8</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.8</version>
      </dependency>
      <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.3.0</version>
      </dependency>
      <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
      </dependency>
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.3.8</version>
      </dependency>
      <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
      </dependency>
      <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.5</version>
      </dependency>
      </dependencies>

然后在创建spring配置文件applicationContext.xml, 并在web.xml配置文件中配置contextConfigListener

1
2
3
4
5
6
7
<context-param>
<param-name>contextConfigListener</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

在spring配置文件中, 配置component scan, 排除扫描Controller, 不与springMVC冲突

1
2
3
4
<!--    spring配置文件, 管理所有业务逻辑组件-->
<context:component-scan base-package="com.atguigu.mybatis">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

然后WEB-INF下创建springMVC配置文件, 配置1component scan只扫描Controller

1
2
3
4
<!--    spring配置文件, 管理所有业务逻辑组件-->
<context:component-scan base-package="com.atguigu.mybatis">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

并配置视图解析器, 和开启注解扫描

1
2
3
4
5
6
7
8
<!--    视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<mvc:annotation-driven></mvc:annotation-driven>
<mvc:default-servlet-handler/>

在spring配置文件中配置数据库连接

1
2
3
4
5
6
7
8
9
<!--    映入配置文件-->
<context:property-placeholder location="dbconfig.properties"/>

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="driverClass" value="${jdbc.driver}" ></property>
<property name="user" value="${jdbc.username}" ></property>
<property name="password" value="${jdbc.password}" ></property>
</bean>

然后在spring配置文件applicationContext中开始整合Mybatis

1
2
3
4
5
<!--    spring事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- configLocation指定全局配置文件的位置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!--mapperLocations: 指定mapper文件的位置-->
<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
</bean>

<!--配置一个可以进行批量执行的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>

<!-- 扫描所有的mapper接口的实现,让这些mapper能够自动注入;
base-package:指定mapper接口的包名
-->
<mybatis-spring:scan base-package="com.atguigu.mybatis.dao"/>
<!-- <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.atguigu.mybatis.dao"></property>
</bean> -->

最后写个Controller和前端jsp页面发送请求

1
2
3
4
5
6
@RequestMapping("/getemps")
public String emps(Map<String,Object> map){
List<Employee> emps = employeeService.getEmps();
map.put("allEmps",emps);
return "list";
}

MyBatis 逆向工程

MBG Code generator 官方文档

MyBatis Generator (MBG) generates code in different styles depending on how it is configured. This is controlled by specifying the targetRuntime attribute on a <context> configuration element. The table below summarizes the different options.

先导入依赖 mybatis-generator-core

To get up and running quickly with MyBatis Generator (MBG), follow these steps:

  1. Create and fill out a configuration file appropriately (see below for samples)

  2. Save the file in some convenient location (like \temp\generatorConfig.xml)

  3. Run MBG from the command line with a command like this:

    1
    2
    java -jar mybatis-generator-core-x.x.x.jar -configfile \temp\generatorConfig.xml -overwrite

    This will tell MBG to run using your configuration file. It will also tell MBG to overwrite any existing Java or Kotlin files with the same name. If you want to save any existing files, then omit the -overwrite parameter. If there is a conflict, MBG will save the newly generated file with a unique name (e.g. MyClass.java.1).

  4. After running MBG, you will need to create or modify the standard MyBatis configuration make use of your newly generated code. See the Tasks After Running MyBatis Generator page for more information.

加入 官方提供的配置模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
<classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />

<context id="DB2Tables" targetRuntime="MyBatis3">
<jdbcConnection driverClass="COM.ibm.db2.jdbc.app.DB2Driver"
connectionURL="jdbc:db2:TEST"
userId="db2admin"
password="db2admin">
</jdbcConnection>

<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>

<javaModelGenerator targetPackage="test.model" targetProject="\MBGTestProject\src">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>

<sqlMapGenerator targetPackage="test.xml" targetProject="\MBGTestProject\src">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>

<javaClientGenerator type="XMLMAPPER" targetPackage="test.dao" targetProject="\MBGTestProject\src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>

<table schema="DB2ADMIN" tableName="ALLTYPES" domainObjectName="Customer" >
<property name="useActualColumnNames" value="true"/>
<generatedKey column="ID" sqlStatement="DB2" identity="true" />
<columnOverride column="DATE_FIELD" property="startDate" />
<ignoreColumn column="FRED" />
<columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />
</table>

</context>
</generatorConfiguration>

<jdbcConnection>指定如何连接到数据库

<javaTypeResolver> 定义JavaBean的生成模型策略

<sqlMapGeneraotr> 定义sql映射生成策略

<javaClientGenerator>指定mapper接口所在的位置

<table> 指定要逆向分析的表格

Running MyBatis Generator

MyBatis Generator (MBG) can be run in the following ways:

把一下代码放进测试类, 然后加上异常处理

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test(){
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("generatorConfig.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
myBatisGenerator.generate(null);
}

运行即可, 系统会生成逆向工程文件

生成的xxxExample 都是封装查询条件

而Criteria就是拼装条件查询, 里面封装了很多方法可以拼接查询条件

MyBatis运行原理

  • Executor
  • StatementHandler
  • ParameterHandler
  • ResultSetHandler

MyBatis插件

在四大对象创建的时候, 每个对象返回之前都是通过interceptorChain.pluginAll(parameterHandler)

获取到所有Interceptor调用plugin方法然后返回包装后的target

这样可以为对象创建一个代理对象, AOP(面向切面)

代理对象就可以拦截四大对象的每一个执行方法

插件实现

  1. 写一个类实现拦截器 implements Interceptor

  2. 第一个方法 intercept

    1
    2
    3
    4
    5

    //执行target方法
    Object proceed = invocation.proceed();
    //然后返回
    return proceed;
  3. plugin方法

    1. 包装对象, 为对象创建代理对象

      1
      2
      3
      //可以借助wrap方法
      Object wrap = Plugin.wrap(target, this);
      return wrap;
  4. setProperties方法 – 讲插件注册时的property属性设置进来

  5. 加上@Interceptor注解 – 完成插件签名, 告诉MyBatis当前类拦截那个对象的哪个方法

    1
    2
    3
    @Interceptor({
    @Signature(type=StatementHandler.class, method="parameterize", args = java.sql.Statement)
    })
  6. 将写好的插件注册到mybatis全局配置文件中

    1
    2
    3
    4
    5
    6
    <plugins>
    <plugin interceptors="全类名">
    <property name="username" value="root"/>

    </plugin>
    </plugins>

MyBatis扩展

分页插件

pageHelper

  • 导入依赖
  • 在MyBatis的xml文件中添加pageintercepor拦截器
1
2
3
4
5
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="reasonable" value="true"/>
</plugin>
</plugins>

使用:

在调用mapper方法之前添加 PageHelpler.startPage(1, 5) 就代表第一页, 每页五条记录, 自动拦截了Mysql

1
Page<Object> page = PageHelper.startPage(1,5);

通过返回的page对象, 还可以获取很多其他参数:

1
2
3
4
5
page.getpageNum(); //当前页码
page.getTotal(); // 总记录数
page.getPageSize();//每页记录数
page.getPages();//总页数

也可以使用PageInfo获取

1
PageInfo<Employee> info  = new PageInfo<>(emps);

这样还能获取更多数据

1
info.isFirstPage()

PageInfo还可以传另一个参数,

1
PageInfo<Employee> info  = new PageInfo<>(emps, 5);

这样可以连续显示5页数据, 第一页第二页显示1-5, 第三页3在中间, 第四页4显示在中间

1
info.getNavigatepageNums();//给出连续显示的页码, 方便解决分页逻辑

批量操作

dafaultExecutorType — BATCH 默认是simple

原生使用:

非批量:

没一条都发一次sql, 预编译一次, 很慢

1
SqlSession openSession = sqlSessionFactory.openSession();

批量:

只预编译一次, 只发送一次Sql

1
 SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

Spring整合配置:

配置一个可以批量执行的sqlSession

1
2
3
4
<bean id="sqlSesion" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessoinFactory" ref = "sqlSessionFactoryBean"></constructor-arg>
<constructor-arg name = "executorType" value"BATCH"></constructor-arg>
</bean>

在Service层里面使用的时候, 直接使用注解自动注入sqlSession就好了

1
2
@AutoWired
private SqlSession sqlSession;

自定义TyeHandler

测试枚举类型的映射.

默认会保存成为枚举的名字. 因为默认使用了, EnumTypeHandler

如果要改变使用EnumOrdinalTypeHandler

去到mybatis全局配置文件

1
2
3
4
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType=""/>
javaType 指定处理特定类型
</typeHandlers>

自定义

在枚举里面, 添加一个构造器

希望数据库里面保存的是我们给枚举对象的特定属性, 就需要一个自定义处理器

自己创建一个类, 然后实现TypeHandler 或者继承BaseTypeHandler

setParameter(PreparedStatement ps,int i, EmpStatus empstatus)里面自定义设置值:

1
ps.setString(i, parameter.getCode().toString());

在getResult(ResultSet rs, String columnName)`里面自定义获取值:

1
2
3
//根据在数据库中拿到的状态码, 返回枚举对象
int code = rs.getInt(columnName);
EmpStatus empStatusByCode = EmpStatus.getEmpStatusByCode(code);//自定义方法

最后在mybatis全局配置文件中配置自定义的处理器就可以了

Author: klenq
Link: https://klenq.github.io/2021/11/09/MyBatis学习笔记/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.