Mybatis学习笔记

1、初识Mybatis

1.1、什么是Mybatis

  • MyBatis 是一款优秀的持久层框架

  • 它支持自定义 SQL、存储过程以及高级映射。

  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

  • MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

如何获得Mybatis?

1.2、持久层

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程

  • 内存:断电及失

  • 数据库(Jdbc),io文件持久化

  • 也可以用生活来举例:冷藏,罐头

为什么需要持久化

  • 有一些对象,不能让它丢掉

  • 内存的价格高昂

1.3、持久层

Dao层,Service层,Controller层

  • 完成持久化工作的代码块

  • 层界限十分明显

1.4、为什么需要Mybatis

  • 帮助程序员将数据存入到数据库中

  • 方便

  • 简化JDBC的操作,自动化

  • 不用Mybatis也可以,只是说使用了Mybatis会更容易上手

  • 优点:

    • 简单易学

    • 灵活

    • sql和代码的分离,提高了可维护性。

    • 提供映射标签,支持对象与数据库的orm字段关系映射

    • 提供对象关系映射标签,支持对象关系组建维护

    • 提供xml标签,支持编写动态sql

    • 使用的人相对来说较多

2、第一个Mybatis程序

思路:搭建环境-->导入Mybatis-->编写代码-->测试

2.1、搭建环境

2.1.1:搭建数据库

CREATE DATABASE `Mybatis` if not EXISTS;
use `Mybatis`;

drop table `user` if EXISTS;

CREATE TABLE `USER`(
`id`  INT(20) not null PRIMARY KEY AUTO_INCREMENT,
`name` varchar(30) DEFAULT null,
`pwd` VARCHAR(30) DEFAULT null,
`birthday` VARCHAR(30) DEFAULT '1900-01-01'
)ENGINE=INNODB DEFAULT CHARSET=utf8;

insert into `USER`(`name`,`pwd`,`birthday`) VALUES
('张三','zhangsan','2008-01-01'),
('李四','lisi','2010-01-01'),
('王五','wangwu','2003-01-01')

2.1.2、新建一个项目

这样项目就建立好了

我们再查看下我们Maven的环境,点击File-->Settings 找到Maven

如果这里是如上图显示的 则最好是修改为自己电脑的地址

下面的User settings file和Local repository无法换位置的记得勾选旁边的Override

删除src目录

 

导入Maven依赖(pom.xml)pom.xml中,未下载成功的依赖会变成红色

<?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.april</groupId>
   <artifactId>MyTestMybatis</artifactId>
   <version>1.0-SNAPSHOT</version>

   <!-- 导入依赖 -->
   <dependencies>
   <!--mysql驱动-->
       <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>8.0.27</version>
       </dependency>
   <!--mybatis-->
       <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
       <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
           <version>3.4.6</version>
       </dependency>
   <!--Juint-->
       <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
           <version>4.12</version>
       </dependency>
   </dependencies>
</project>

2.1.3、创建一个模块

在主项目上右键->new->Module...

这样我们就可以看到 他这里是有两个pom.xml的 一个是总的 我们所有里面的项目都可以调用这个总的pom.xml 我们叫他为父工程

再看到这里的parent 我们可以看到 这个就是我最开始新建项目的名字 也就是他的父工程

在Resource目录下新建一个文件 名字为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:核心配置文件-->
<configuration>
   <!--default默认环境-->
   <environments default="development">
       <environment id="development">
           <!--transactionManager:事务管理-->
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
               <property name="url" value="jdbc:mysql://localhost:3306/mybatis?userUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false&amp;serverTimezone=GMT%2B8"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           </dataSource>
       </environment>
   </environments>
</configuration>

我们再新建几个目录和一个MybatisUtil.java文件,截图如下:

其中MybatisUtil.java的代码如下:

package com.april.utils;


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 java.io.IOException;
import java.io.InputStream;

public class MybatisUtil {
   private static SqlSessionFactory sqlSessionFactory;
   static {
           try {
               // 获取resource目录下的mybatis-config.xml文件
               String resource = "mybatis-config.xml";
               InputStream inputStream = Resources.getResourceAsStream(resource);
               sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
          } catch (IOException e) {
               e.printStackTrace();
          }

  }

   // 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
   // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
   public static SqlSession getSqlSession(){
       return sqlSessionFactory.openSession();
  }
}

2.2、编写实际性代码

实体类

在pojo目录下,新建一个User的java类,这个类是让你编写你数据库中数据的属性的,代码如下:

package com.april.pojo;

public class User {
   private int id;
   private String name;
   private String pwd;
   private String birthday;

   public User() {
  }

   public User(int id, String name, String pwd, String birthday) {
       this.id = id;
       this.name = name;
       this.pwd = pwd;
       this.birthday = birthday;
  }

   public int getId() {
       return id;
  }

   public String getName() {
       return name;
  }

   public String getPwd() {
       return pwd;
  }

   public String getBirthday() {
       return birthday;
  }

   public void setId(int id) {
       this.id = id;
  }

   public void setName(String name) {
       this.name = name;
  }

   public void setPwd(String pwd) {
       this.pwd = pwd;
  }

   public void setBirthday(String birthday) {
       this.birthday = birthday;
  }

   @Override
   public String toString() {
       return "User{" +
               "id=" + id +
               ", name='" + name + '\'' +
               ", pwd='" + pwd + '\'' +
               ", birthday='" + birthday + '\'' +
               '}';
  }
}

mapper接口

在mapper这个文件夹中 新建一个UserMapper的Interface类,代码如下:

package com.april.mapper;

import com.april.pojo.User;

import java.util.List;

public interface UserMapper {
   List<User> getUserList();
}

继续在mapper这个文件夹下面 新建一个UserMapper.xml文件 用于实现mapper接口

<?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:命名空间 绑定一个对应的mapper接口-->
<mapper namespace="com.april.mapper.UserMapper">
   <!--id里面的内容对应你UserMapper的方法名 相当于重写方法 resultType为你调用的类名-->
   <select id="getUserList" resultType="com.april.pojo.User">
      select * from mybatis.user
   </select>
</mapper>

2.3、测试

注意点

  • Junit测试

这时候我们在test目录下新建一个和上方一样的路径,并新建一个UserMapperTest.java文件

在里面写上如下代码并运行:

package com.april.mapper;

import com.april.pojo.User;
import com.april.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {
@Test
public void test(){
// 获得Sqlsession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
// 执行SQL 从sqlsession中获取mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 现在就可以直接从mapper中调用UserMapper的方法了 由于我们的xml文件的id直接绑定的是UserMapper的方法 所以我们直接调用内部的方法即可
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
// 关闭sqlsession
sqlSession.close();

}
}

这时候我们会发现一个报错org.apache.ibatis.binding.BindingException: Type interface com.april.mapper.UserMapper is not known to the MapperRegistry.,原因是我们没有注册mappers

这时候我们只需要在我们的mybatis-config.xml中加上

<mappers>
<mapper resource="com/april/mapper/UserMapper.xml"/>
</mappers>

合起来就是这个样子

<?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:核心配置文件-->
<configuration>
<!--default默认环境-->
<environments default="development">
<environment id="development">
<!--transactionManager:事务管理-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbcstudy?userUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false&amp;serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/april/mapper/UserMapper.xml"/>
</mappers>
</configuration>

重新运行UserMapperTest会有java.lang.ExceptionInInitializerError

这是因为我们找不到我们的UserMapper.xml,因为我们java会生成class文件运行 所以需要我们target目录下有UserMapper.xml,需要在pom.xml文件中加上如下内容(建议以后所有的MAVEN项目先把这个加上)

<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

合起来是这个样子:

<?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">
<parent>
<artifactId>MyTestMybatis</artifactId>
<groupId>com.april</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>MybatisTest1</artifactId>
<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

</project>

然后在命令行输入如下命令:mvn cleanmvn install

重新启动main方法即可

3、CRUD

现在我们根据ID去查一个user 这时候 我们只需要改三个地方:

1 UserMapper.java

2 UserMapper.xml

3 UserMapperTest.java

3.1、Select 查询

首先 我们修改UserMapper.java文件

在上面添加上一个方法:

// 根据ID查询用户
User getUserById(int id);

然后我们再修改UserMapper.xml文件,在<mapper></mapper>中补充:

<select id="getUserById" resultType="com.april.pojo.User" parameterType="int">
select * from mybatis.user where id = #{id}
</select>
  • namespace的包名要和接口中的包名一致!

  • id:就是对应的namespace中的方法

  • resultType:就是对应namespace中 方法的返回值/Sql语句执行的返回值

  • parameterType:参数类型

然后再在userMapperTest.java文件中 加入如下代码

 @Test
public void testGetUserById(){
// 获得SqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}

3.2、Insert 添加

同理 我们可以写出如下语句:

  1. UserMapper.java中加上如下方法:

// 插入一个用户
int addUser(User user);
  1. UserMapper.xml<mapper></mapper>中补充如下代码:

<insert id="addUser" parameterType="com.april.pojo.User">
insert into mybatis.user (id,name,pwd,birthday) values (#{id},#{name},#{pwd},#{birthday})
</insert>
  1. 然后再在userMapperTest.java文件中 加入如下代码:

// 增删改需要提交事务
@Test
public void testAddUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int rs = mapper.addUser(new User(4,"赵柳","456789","1949-10-01"));
if (rs>0){
System.out.println("插入成功");
sqlSession.commit();
}
System.out.println(rs);
sqlSession.close();
}

3.3、Update 修改

  1. UserMapper.java中加上如下方法:

// 修改用户名字
int updateUser(User user);
  1. UserMapper.xml<mapper></mapper>中补充如下代码:

<update id="updateUser" parameterType="com.april.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd},birthday=#{birthday} where id = #{id}
</update>
  1. 然后再在userMapperTest.java文件中 加入如下代码:

 @Test
public void testUpdateUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int rs = mapper.updateUser(new User(4,"朱毅","955487","1959-10-01"));
if (rs>0){
System.out.println("修改成功");
sqlSession.commit();
}
System.out.println(rs);
sqlSession.close();
}

3.4、Delete 删除

  1. UserMapper.java中加上如下方法:

// 删除用户
int deleteUser(int id);
  1. UserMapper.xml<mapper></mapper>中补充如下代码:

<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id}
</delete>
  1. 然后再在userMapperTest.java文件中 加入如下代码:

@Test
public void testDeleteUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int rs = mapper.deleteUser(4);
if (rs>0){
System.out.println("删除成功!");
sqlSession.commit();
}
System.out.println(rs);
sqlSession.close();
}

这样,一个简单的增删改查就全部完成啦

下面附上完整版源代码:

  1. MybatisUtil.java

package com.april.utils;


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 java.io.IOException;
import java.io.InputStream;

public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 获取resource目录下的mybatis-config.xml文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}

}

// 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
  1. User.java

package com.april.pojo;

public class User {
private int id;
private String name;
private String pwd;
private String birthday;

public User() {
}

public User(int id, String name, String pwd, String birthday) {
this.id = id;
this.name = name;
this.pwd = pwd;
this.birthday = birthday;
}

public int getId() {
return id;
}

public String getName() {
return name;
}

public String getPwd() {
return pwd;
}

public String getBirthday() {
return birthday;
}

public void setId(int id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

public void setPwd(String pwd) {
this.pwd = pwd;
}

public void setBirthday(String birthday) {
this.birthday = birthday;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
", birthday='" + birthday + '\'' +
'}';
}
}
  1. UserMapper.java

package com.april.mapper;

import com.april.pojo.User;

import java.util.List;

public interface UserMapper {
// 查询全部用户
List<User> getUserList();

// 根据ID查询用户
User getUserById(int id);

// 插入一个用户
int addUser(User user);

// 修改用户名字
int updateUser(User user);

// 删除用户
int deleteUser(int id);
}
  1. UserMapper.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:命名空间 绑定一个对应的mapper接口-->
<mapper namespace="com.april.mapper.UserMapper">
<!--id里面的内容对应你UserMapper的方法名 相当于重写方法 resultType为你调用的类名-->
<select id="getUserList" resultType="com.april.pojo.User">
select * from mybatis.user
</select>

<select id="getUserById" resultType="com.april.pojo.User" parameterType="int">
select * from mybatis.user where id = #{id}
</select>

<insert id="addUser" parameterType="com.april.pojo.User">
insert into mybatis.user (id,name,pwd,birthday) values (#{id},#{name},#{pwd},#{birthday})
</insert>

<update id="updateUser" parameterType="com.april.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd},birthday=#{birthday} where id = #{id}
</update>

<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id}
</delete>
</mapper>
  1. UserMapperTest.java

package com.april.mapper;

import com.april.pojo.User;
import com.april.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {
@Test
public void test(){
// 获得SqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
try {
// 执行SQL 从sqlSession中获取mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 现在就可以直接从mapper中调用UserMapper的方法了 由于我们的xml文件的id直接绑定的是UserMapper的方法 所以我们直接调用内部的方法即可
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
}finally {
// 关闭sqlSession
sqlSession.close();
}
}

@Test
public void testGetUserById(){
// 获得SqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}

// 增删改需要提交事务
@Test
public void testAddUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int rs = mapper.addUser(new User(4,"赵柳","456789","1949-10-01"));
if (rs>0){
System.out.println("插入成功");
sqlSession.commit();
}
System.out.println(rs);
sqlSession.close();
}

@Test
public void testUpdateUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int rs = mapper.updateUser(new User(4,"朱毅","955487","1959-10-01"));
if (rs>0){
System.out.println("修改成功");
sqlSession.commit();
}
System.out.println(rs);
sqlSession.close();
}

@Test
public void testDeleteUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int rs = mapper.deleteUser(4);
if (rs>0){
System.out.println("删除成功!");
sqlSession.commit();
}
System.out.println(rs);
sqlSession.close();
}
}

3.5、错误分析

  • 标签匹配错误[UserMapper.xml的insert标签错写成select]

  • mybatis-config.xml的mapper resource 使用的是点而不是斜杠

  • 程序配置文件符合复合规范

  • MybatisUtil类中,已经定义了一个私有的sqlSessionFactory,但还在静态代码块中使用一个新的SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

  • 输出的xml文件存在中文乱码问题[格式为GBK或者ASNI格式等]

  • maven没有导出问题

4、Map和模糊查询扩展

4.1、使用场景

当你使用update的时候 例如说,我们数据库中有100个字段,而我们只想要修改一个或者几个字段的时候 我们就没办法在UserMapper的parameterType中使用User类 这时候我们就可以用Map,又或者是我们需要查询多条数据的时候(虽然不是很正规,但是是一个比较万能的方式)

4.2、具体代码

代码如下:

UserMapper.java内容如下:

// 使用Map添加数据
// 好处:我可以不用知道这个map中有什么
int addUserForMap(Map<String,Object> map);

UserMapper.xml内容如下:

 <!--map类型直接在parameterType写map即可 后面的#{id}之类的,可以和数据库的不一致,为了方便理解 我在这里全部加上user-->
<insert id="addUserForMap" parameterType="map">
insert into mybatis.user (id,name,pwd,birthday) values (#{userid},#{username},#{userpwd},#{userbirthday})
</insert>

UserMapperTest如下:

 @Test
public void testAddUserForMap(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("userid",4);
map.put("username","王若");
map.put("userpwd","ayjsihdu");
map.put("userbirthday","1999-01-01");
mapper.addUserForMap(map);
sqlSession.close();
}

4.3、注意事项

Map传递参数,直接在sql中取出我们的key即可!【parameterType="map"】

对象传递参数,直接在sql中取对象的属性即可!【parameterType="Object"】

只有一个基本类型参数的情况下,可以直接在sql中取到! 举例如下:

<select id="getUserById" resultType="com.april.pojo.User" parameterType="int">
select * from mybatis.user where id = #{id}
</select>
<!--可以直接变成如下这个样子:-->
<select id="getUserById" resultType="com.april.pojo.User">
select * from mybatis.user where id = #{id}
</select>

多个参数用Map,或者注解!

4.4、模糊查询

模糊查询的方式:

UserMapper.java:

// 使用模糊查询查询用户
List<User> getUserLike(String value);

UserMapper.xml:

<select id="getUserLike" resultType="com.april.pojo.User">
select * from mybatis.user where name like concat('%',#{value},'%')
</select>

UserMappperTest.java:

@Test
public void getUserLike(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserLike("王");
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}

5、Mybatis配置解析

前言

以下文字部分来源于官方文档 地址:[https://mybatis.org/mybatis-3/zh/configuration.html]

5.1、核心配置文件

5.1.1、核心配置文件

  • mybatis-config.xml(名字可能不一样,官方建议使用此名字)

  • 官方说明:MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。

官方文档解释如下:

5.1.2、configuration顺序

这是在我们的xml文件中<configuration>标签中,各种子标签的顺序,如果没有则跳过

properties > settings > typeAliases > typeHandlers > objectFactory > objectWrapperFactory > reflectorFactory > plugins > environments > databaseIdProvider > mappers

5.2、环境配置(environments)

环境配置

  • MyBatis 可以配置成适应多种环境

  • 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

  • 一般只拥有两个环境:测试环境,正式环境

我们写一个测试的mybatis-config.xml(这里将默认环境修改为了test):

<?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:核心配置文件-->
<configuration>
<!--default默认环境-->
<environments default="test">
<environment id="development">
<!--transactionManager:事务管理-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?userUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false&amp;serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
<!--id为你这个环境的名字(在上面的default="development"进行修改,将development换为你要切换的环境即可)-->
<environment id="test">
<!--transactionManager:事务管理器-->
<transactionManager type="JDBC"/>
<!--dataSource:数据源-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?userUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false&amp;serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/april/mapper/UserMapper.xml"/>
</mappers>
</configuration>

注意一些关键点:

  • 默认使用的环境 ID(比如:default="development")。

  • 每个 environment 元素定义的环境 ID(比如:id="development")。

  • 事务管理器的配置(比如:type="JDBC")。

  • 数据源的配置(比如:type="POOLED")。

默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

事务管理器(transactionManager)

在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

    <transactionManager type="MANAGED">
    <property name="closeConnection" value="false"/>
    </transactionManager>

如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。

数据源(dataSource)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

  • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

  • driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。

  • url – 这是数据库的 JDBC URL 地址。

  • username – 登录数据库的用户名。

  • password – 登录数据库的密码。

  • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。

  • defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息。

作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:

  • driver.encoding=UTF8

这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8encoding 属性给数据库驱动。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

  • poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10

  • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。

  • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)

  • poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。

  • poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnectionspoolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)

  • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。

  • poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。

  • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。

总结

UNPOOLED与POOLED的差别:

是否有池的概念,池:用完回收等下一个人连接数据库的时候继续使用,不会将其进行注销;

并且要学会使用配置多套运行环境(上面有代码)[测试的时候可以换着来,但是最终只能实现一个];

重点理解:Mybatis默认的事务管理器就是JDBC,默认的数据源是POOLED;

5.3、属性(properties)

前言

我这里使用了第三章CRUD的代码进行测试,在这个之前你们可以把整个第三章的代码进行复制出来

我们可以通过properties属性来实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

编写一个配置文件(db.propertites):

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?userUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username=root
password=123456

在核心配置文件(mybatis-config.xml)中引入[按照我最上面的configuration优先级进行顺序的编写]

<?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:核心配置文件-->
<configuration>
<!--
由于我们是在同级目录 所以我可以直接写db.properties 如果不在同级目录则需要写上全路径(用/隔开)
我们可以不用写里面的`property name="username"`之类的 我们直接用下面的方法来引入配置文件就可以
-->
<properties resource="db.properties"/>
<!--default默认环境-->
<environments default="development">
<environment id="development">
<!--transactionManager:事务管理-->
<transactionManager type="JDBC"/>
<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>
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/april/mapper/UserMapper.xml"/>
</mappers>
</configuration>

使用官方文档的读取的话,我们可以去掉我们的username和password:

db.propertites代码如下:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?userUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8

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:核心配置文件-->
<configuration>
<!--
由于我们是在同级目录 所以我可以直接写db.properties 如果不在同级目录则需要写上全路径(用/隔开)
-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<!--default默认环境-->
<environments default="development">
<environment id="development">
<!--transactionManager:事务管理-->
<transactionManager type="JDBC"/>
<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>
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/april/mapper/UserMapper.xml"/>
</mappers>
</configuration>

结果:上面的两种方法都可以跑出来

优先级测试

db.propertites代码如下:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?userUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username=root
password=123456

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:核心配置文件-->
<configuration>
<!--
由于我们是在同级目录 所以我可以直接写db.properties 如果不在同级目录则需要写上全路径(用/隔开)
-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="654321"/>
</properties>
<!--default默认环境-->
<environments default="development">
<environment id="development">
<!--transactionManager:事务管理-->
<transactionManager type="JDBC"/>
<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>
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/april/mapper/UserMapper.xml"/>
</mappers>
</configuration>

如果运行测试失败,则证明xml中的property属性优先级要高一点

测试结果为正确跑出了数据,则证明直接调用外部配置文件的优先级会比直接在xml中写死的优先级要高(首先读取在 mybatis-config.xml中的properties 元素体内指定的属性,最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。)

总结

  • 可以直接引入外部文件

  • 可以在其中增加一些属性配置

  • 如果两个文件有同一个字段,则会先读取配置方法中的字段,再读取配置文件的字段,如果有同名的属性会通过配置文件的属性进行覆盖

5.4、类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。这样在我们的UserMapper.xml中,原本是这么写的:

<select id="getUserList" resultType="com.april.pojo.User">
select * from mybatis.user
</select>

如果我们给类型别名配置完成之后,就可以这么写:

<select id="getUserList" resultType="User">
select * from mybatis.user
</select>

代码如下

第一种方式

typeAlias:为一个具体的类设置单独的一个别名

mybatis-config.xml(里面的alias和type根据你实际的包名和类名进行切换):

<!--typeAliases类型别名-->
<typeAliases>
<!--这其中有两个使用方式,第一种是package,第二种是typeAlias,我们先使用typeAlias进行测试-->
<typeAlias type="com.april.pojo.User" alias="User"></typeAlias>
</typeAliases>

UserMapper.xml:

<!--将<select id="getUserList" resultType="com.april.pojo.User">改为<select id="getUserList" resultType="User">-->
<select id="getUserList" resultType="User">
select * from mybatis.user
</select>

第二种方式

package:MyBatis 会在包名下面搜索需要的 Java Bean(扫描实体类的包),它的默认别名就为这个类的类名,首字母建议小写!(优点是不用配置多个类)

mybatis-config.xml:

<typeAliases>
<package name="com.april.pojo"/>
</typeAliases>

UserMapper.xml:

<select id="getUserList" resultType="user">
select * from mybatis.user
</select>

具体使用情况

在实体类比较少的时候,使用第一种方式

如果实体类十分多,建议使用方式

优缺点:第一种可以DIY别名,第二种DIY别名有点麻烦,需要使用注解,详情如下:

每一个使用 package的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("hello")
public class User {
...
}

这时候我们将resultType="user"改为resultType="hello"就可以正常运行了(如果还是报错则检查是否所有user都改为hello了)

<select id="getUserList" resultType="user">
select * from mybatis.user
</select>

常见的 Java 类型内建的类型别名

下面是一些为常见的 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

5.5、设置(settings)

这是 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' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 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。(假设数据库列名是last_name,如果开启了这个规则则会自动转为为lastName)[数据库使用_的原因是因为之前使用的是oracle数据库,他会自动把所有字段转成大写,所以在那时候就已经有些惯性思维,数据库中的字段都用下划线进行分割] 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(deprecated since 3.5.9) | 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
nullableOnForEach Specifies the default value of 'nullable' attribute on 'foreach' tag. (Since 3.5.9) true | false false

5.6、其他配置

  • typeHandlers(类型处理器)

  • objectFactory(对象工厂

  • plugins(插件)

    • mybatis-generator-core(是一款可以自动生成mybatis代码的文件)

    • mybatis-plus(mybatis的第三方插件,使用可以让mybatis的效率更高)

    • 通用mapper

5.7、映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件(在mapper元素解析的时候 我们会用到这个类)

方式一【推荐使用】

<mappers>
<mapper resource="com/april/mapper/UserMapper.xml"/>
</mappers>

这个是目前来说,我们最常用的方式,现在介绍一下其他的几种方式:

方式二 使用class文件绑定注册

<mappers>
<mapper class="com.april.mapper.UserMapper"/>
</mappers>

使用mapper class的注意点:

  • 接口和它的Mapper配置必须同名

  • 接口和它的Mapper配置必须在同一个包下

方式三 使用扫描包进行注入绑定

<mappers>
<package name="com.april.mapper"/>
</mappers>
  • 这个方式和我们类型别名的package其实是差不多的,都是直接扫描这个下面的所有包,从而进行注册

  • 但是:这个接口和它的Mapper配置也是必须同名的 不然会出现(not found)的这个错误

  • 如果放到resource目录下的话 在resource目录下同包 同名 则也是没有问题的 如果直接放在resource目录下则会报错

5.8、生命周期和作用域

生命周期,和作用域都是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder:

  • 一旦创建了SqlSessionFactory,就不再需要它了

  • 局部变量

SqlSessionFactory:

  • 说白了就是理解为/想象为数据库连接池

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

  • 因此 SqlSessionFactory 的最佳作用域是应用作用域。最简单的就是使用单例模式或者静态单例模式。

  • 也就是说 SqlSessionFactory为一个全局变量,程序开始的时候即为开始,程序结束的时候即为结束

SqlSession:

  • 连接到连接池的一个请求

  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。(放到方法中即可)

  • 用完之后需要赶紧关闭,否则资源将被占用!

  • 这里面的每一个Mapper,就代表一个具体的业务(insert update delete select)

6、ResultMap

6.1、问题介绍

数据库中的字段,我们现在是这样的:

现在我们新建一个项目,拷贝之前的,测试实体类字段不一致的情况(记得重写构造方法)

将pwd的字段名改为password,然后根据ID查询数据

然后发现 这里的password为null

原因:

我们UserMapper.xml中是这么写的:select * from mybatis.user where id = #{id}

但程序中实际是这么认为的:select id,name,pwd,birthday from mybatis.user where id = #{id}

按照逻辑 我们获取到的是数据库的password字段,而并非pwd字段

6.2、解决方法

第一种(最简单粗暴,也是最笨的解决方式) :起别名

select id,name,pwd as password,birthday from mybatis.user where id = #{id}

第二种 使用resultMap

1 先将UserMapper.xml中的sql改回来

2 将UserMapper.xml中的resulttype改为resultMap(通过结果集映射)

数据库中的样子idpwdnamebirthday
java代码中的样子 id password name birthday

结果如图:我们原本调用的是一个resultType,现在我们改成了resultMap,并在mapper标签中定义了一个resultMap,说简单一点也就是将数据库的列,通过这个映射到属性库中的字段中

  • resultMap 元素是 MyBatis 中最重要最强大的元素。

  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

当然,上述方式只是简单的将所有的列映射到HashMap的键上,这个会由我们的resultType属性指定。虽然在大部分情况下完全够用,但是 HashMap 并不是一个很好的领域模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。MyBatis 对两者都提供了支持。

7、日志

7.1、日志工厂

如果一个数据库操作,出现了异常,我们需要排查错误,日志就是最好的助手

曾经:System.out.println();,debug

现在:日志工厂!

  • SLF4J

  • LOG4J(deprecated since 3.5.9)

  • LOG4J2(log4j的升级版)

  • JDK_LOGGING (JAVA自带日志输出)

  • COMMONS_LOGGING (工具包输出)

  • STDOUT_LOGGING(控制台输出)

  • NO_LOGGING(无日志输出)

在Mybatis中,具体使用哪一个日志实现,在设置中设定!

日志配置(STDOUT)

这就是STDOUT的输出结果:

Opening JDBC Connection	// 打开JDBC
Created connection 2143431083. //创建连接对象
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7fc229ab]
==> Preparing: select * from mybatis.user where id = ? // SQL语句
==> Parameters: 1(Integer) // 参数
<== Columns: id, name, pwd, birthday //列
<== Row: 1, 张三, zhangsan, 2008-01-01 //列对象
<== Total: 1
User{id=1, name='张三', password='zhangsan', birthday='2008-01-01'} // 查询结果
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7fc229ab]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7fc229ab] //关闭JDBC
Returned connection 2143431083 to pool.

7.2、Log4j

什么是Log4j

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件,或者GUI组件

  • 我们也可以控制每一条日志的输出格式

  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。

  • 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

7.2.1、 导入log4j的包

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

7.2.2、 log4j.properties

# 将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

# 控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 日志输出的格式
log4j.appender.console.layout.ConversionPattern=【%c】-%m%n

# 文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
# 文件设置的地址
log4j.appender.file.File=./log/april.log
# 日志的最大文件 如果超过10mb则生成一个新的文件
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yyyy-MM-dd hh:mm:ss}][%c]%m%n

# 日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

7.2.3、 配置log4j为日志的实现(空格不要多,大小写不要错)

<settings>
<!--标准的日志工厂实现-->
<!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
<setting name="logImpl" value="LOG4J"/>
</settings>

7.2.4、 Log4j的使用

简单使用

我们在测试类中再写一个test

1 在要使用Log4j的类中,导入包 import org.apache.log4j.Logger;

2 日志对象,加载参数为当前类的class

3 如图,这样进行打印即可

8、分页

分页的原因:减少数据的处理量

8.1、使用Mybatis实现Limit分页

SQL语法

select * from user limit startindex,pageSize

-- 每一页显示两个 从第0个开始查
select * from user limit 0,2;
-- 查询前2条记录
select * from user limit 2; #[0,n]

1 接口代码

// 分页实现查询
List<User> getUserByLimit(Map<String,Integer> map);

2 Mapper.xml

<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from user limit #{startIndex},#{pageSize}
</select>

3 测试类

 @Test
public void testLimit(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);
map.put("pageSize",2);
List<User> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}

8.2、分页插件

前言

在你的项目没有足够大的时候,不建议使用分页插件;

 

使用方法:https://pagehelper.github.io/docs/howtouse/

了解即可,万一以后公司需要使用,你需要知道他是什么东西

9、使用注解开发

9.1、面向接口编程

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性和方法;

  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现;

  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构;

9.2、使用注解进行实际开发

9.2.1、实际应用

将上一个项目复制过来,并只留下这些文件,然后将日志输出改为标准的日志工厂模式,再将mappers这个标签给去除掉,重新创建一个mapper文件夹,里面包含一个UserMapper的接口

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:核心配置文件-->
<configuration>
<!--
由于我们是在同级目录 所以我可以直接写db.properties 如果不在同级目录则需要写上全路径(用/隔开)
-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<settings>
<!--标准的日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--typeAliases类型别名-->
<typeAliases>
<!--这其中有两个使用方式,第一种是package,第二种是typeAlias,我们先使用typeAlias进行测试-->
<!-- <typeAlias type="com.april.pojo.User" alias="User"></typeAlias>-->
<package name="com.april.pojo"/>
</typeAliases>
<!--default默认环境-->
<environments default="development">
<environment id="development">
<!--transactionManager:事务管理-->
<transactionManager type="JDBC"/>
<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>

<mappers>
<mapper class="com.april.mapper.UserMapper"/>
</mappers>

</configuration>

UserMapper.java

package com.april.mapper;

import com.april.pojo.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper {

@Select("select * from user")
List<User> getUsers();
}

UserMapperTest.java

package com.april.mapper;

import com.april.pojo.User;
import com.april.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {

@Test
public void test(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}

sqlSession.close();

}
}

执行结果如下:

我们可以看到,我们之前修改的password,现在仍然为空,因为我们的注解只适用于一些简单的sql,复杂一点的sql的话我们还是需要使用xml进行配置与使用

本质:反射机制实现

底层:动态代理!

9.2.2、Mybatis详细的执行流程

 

9.3、注解增删改查

我们可以在工具类创建的时候自动提交事务!只需要在MybatisUtil里面修改如下代码(注释为之前的代码):

但是!一般不要设置自动提交,如果自己代码出了问题导致数据库出现问题,不自动提交也方便回滚,我这里测试的话 还是加上自动注入,平常使用的时候注意一点即可

完整增删改查代码如下:

MybatisUtil.java:

package com.april.utils;


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 java.io.IOException;
import java.io.InputStream;

public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 获取resource目录下的mybatis-config.xml文件
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}

}

// 既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
// public static SqlSession getSqlSession(){
// return sqlSessionFactory.openSession();
// }
}

UserMapper.java:

package com.april.mapper;

import com.april.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserMapper {

// 查询所有User
@Select("select * from user")
List<User> getUsers();

// 根据ID查询 基本类型使用注解的时候 都建议加上@Param
// 方法存在多个参数的话,所有的参数前面必须加上@Param注解
@Select("select * from user where id = #{id} and name = #{name}")
User getUserByParam(@Param("id") int id,@Param("name") String name);

// 引用对象无需写@param #{}里面应用的参数名要和实体类中一致
@Insert("insert into user(id,name,pwd,birthday) values (#{id},#{name},#{password},#{birthday})")
int addUser(User user);

@Update("update user set name=#{name},pwd=#{password},birthday=#{birthday} where id = #{id}")
int updateUser(User user);

// 这里将param改为uid,是为了理解,注解和mapper.xml中,调用的#{},里面都是使用的@param中的参数名,如果没有填写,才使用方法中的参数名
@Delete("delete from user where id = #{uid}")
int deleteUser(@Param("uid") int id);
}

UserMapperTest.java:

package com.april.mapper;

import com.april.pojo.User;
import com.april.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {

@Test
public void test(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}

@Test
public void testSelectByParam(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User res = mapper.getUserByParam(1, "张三");
System.out.println(res);
sqlSession.close();
}

@Test
public void testInsertUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 因为我们在配置之后,我们可以无需手动提交,而改为自动提交了
mapper.addUser(new User(5,"爪巴","10086","2022-01-01"));
sqlSession.close();
}

@Test
public void testUpdateUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(5,"四月一","10086","2022-01-01"));
sqlSession.close();
}

@Test
public void testDeleteUser(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(5);
sqlSession.close();
}
}

10、Lombok

10.1、使用步骤

1 在IDEA中安装Lombok插件

2 在项目中导入lombok的jar包(从maven仓库下载下来的记得删掉scope)

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>

3 在实体类上加上注解即可!

@Getter and @Setter
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@Data
==== 以上是暂时需要掌握的 ====
==== 以下是Lombok带的其他方法 ====
@FieldNameConstants
@RequiredArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)

@Data:生成了无参构造、get、set、toString、hashCode、equals方法

@AllArgsConstructor:生成了有参构造的方法

@NoArgsConstructor:生成了无参构造的方法

package com.april.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// data这个注解自带get/set方法
@Data
// 有参构造
@AllArgsConstructor
// 无参构造
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
private String birthday;

}

10.2、Lombok的优缺点

优点

  1. 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率

  2. 让代码变的简洁,不用过多的去关注相应的方法

  3. 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等

缺点

  1. 不支持多重参数构造器的重载

  2. 虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度

10.3、总结

Lombok虽然有很多优点,但是Lombok更类似于一种IDE插件,项目也需要依赖相应的jar包,Lombok依赖jar包是因为编译的时候需要用他的注解,说他类似插件的原因是因为在使用的时候,eclipse和IDEA都需要安装相应的插件,在编辑器编译的时候通过操作AST(抽象语法树)改变字节码生成,变向的就是说在改变java语法。

它不像spring的依赖注入或者mybatis的ORM一样是运行时的特性,而是编译时的特性。因为Lombok主要是靠的对插件的依赖,而且只是省去了一些人工生成代码的麻烦,但现在的IDE都有快捷键来协助生成我们的getter/setter等方法,也非常方便。

知乎上有位大神发表过对L ombok的一些看法: 这是一种低级趣味的插件,不建议使用。 JAVA发展到今天, 各种插件层出不穷,如何甄别各种插件的优劣?能从架构上优化你的设计的,能提高应用程序性能的,实现高度封装可扩展的....像Lombok这种, 像这种插件,已经不仅仅是插件了,改变了你如何编写源码,事实上,少去了的代码,写上去又如何?如果JAVA家族到处充斥这样的东西, 那只不过是一坨披着金属颜色的屎,迟早会被其它的语言取代。

虽然话糙但理确实不糙,试想一个项目有非常多类似Lombok这样的插件,个人觉得真的会极大的降低阅读源代码的舒适度。虽然非常不建议在属性的getter/setter写一些业务代码, 但在多年项目的实战中,有时通过给getter/setter加一 点点业务代码,能极大的简化某些业务场景的代码。所谓取舍,也许就是这时的舍弃一定的规范,取得极大的方便。

我现在非常坚信一条理念, 任何编程语言或插件,都仅仅只是工具而已,即使工具再强大也在于用的人,就如同小米加步枪照样能赢飞机大炮的道理一样。结合具体业务场景和项目实际情况,无需一 味追求高大上的技术,适合的才是王道。

Lombok有它的得天独厚的优点,也有它避之不及的缺点,熟知其优缺点,在实战中灵活运用才是王道。

11、多对一处理

11.1、讲解/准备

假设左边是学生 右边是老师

  • 多个学生 对应一个老师

  • 对于学生这边而言,我们用关联来代表这里的关系,也就是说,多个学生只有一个老师叫关联【多对一】

  • 对于老师这边而言,我们用集合来代表这里的关系,也就是说,一个老师有很多个学生叫集合【一对多】

SQL

CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师');

CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

这时候我们可以看到,学生表中有5个学生,他们关联着同一个老师,老师表里只有一个老师,但它关联着五个学生

我们新建一个项目:

将MybatisTest5中的resources下面的db.propertiesmybatis-config.xml拷贝过来,再将mybatis-config.xml中的mappers删除,这样我们就获得了一个干净的项目了;

11.2、测试环境搭建

1 新建Teacher,Student表的实体类

  • Teacher.java:

package com.april.pojo;

public class Teacher {
private Integer id;
private String name;

public Teacher() {
}

@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
  • Student.java

package com.april.pojo;

public class Student {
private Integer id;
private String name;
// 学生需要关联一个老师
private Teacher teacher;

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", teacher=" + teacher +
'}';
}

public Student() {
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Teacher getTeacher() {
return teacher;
}

public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
}

2 建立Mapper接口

  • TeacherMapper.java

package com.april.mapper;

import com.april.pojo.Teacher;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface TeacherMapper {

@Select("select * from teacher where id = #{tid}")
Teacher getTeacher(@Param("tid") int id);

}
  • StudentMapper.java(因为没做查询 所以暂时没有数据)

package com.april.mapper;

public interface StudentMapper {
}

3 建立Mapper.xml文件(放到resources目录下的com/april/mapper文件夹下【和java目录下同级】)

请注意:这里的头部和我们的mybatis-config.xml不一致,请注意区分,导入的是mapper的dtd约束文件

  • TeacherMapper.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">
<!--configuration:核心配置文件-->
<mapper namespace="com.april.mapper.TeacherMapper">

</mapper>
  • StudentMapper.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">
<!--configuration:核心配置文件-->
<mapper namespace="com.april.mapper.StudentMapper">

</mapper>

4 在mybatis-config.xml中注册我们的Mapper【可以用mapper resource,也可以用mapper class,根据自己喜好进行选择】

<?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:核心配置文件-->
<configuration>
<!--
由于我们是在同级目录 所以我可以直接写db.properties 如果不在同级目录则需要写上全路径(用/隔开)
-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<settings>
<!--标准的日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--typeAliases类型别名-->
<typeAliases>
<!--这其中有两个使用方式,第一种是package,第二种是typeAlias,我们先使用typeAlias进行测试-->
<!-- <typeAlias type="com.april.pojo.User" alias="User"></typeAlias>-->
<package name="com.april.pojo"/>
</typeAliases>
<!--default默认环境-->
<environments default="development">
<environment id="development">
<!--transactionManager:事务管理-->
<transactionManager type="JDBC"/>
<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>

<mappers>
<mapper resource="com/april/mapper/StudentMapper.xml"></mapper>
<mapper resource="com/april/mapper/TeacherMapper.xml"></mapper>
</mappers>
</configuration>

5 测试查询

import com.april.mapper.TeacherMapper;
import com.april.pojo.Teacher;
import com.april.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;

public class MyTest1 {
public static void main(String[] args) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
sqlSession.close();
}
}

11.3、嵌套处理

我们现在需要查询学生表,并将老师表中老师的数据也一并查询出来,数据库语句类似于如下的效果

select s.id,s.name,t.namefrom student s,teacher t where s.tid = t.id

11.3.1、按照查询嵌套处理

测试查询还是一样的代码

import com.april.mapper.TeacherMapper;
import com.april.pojo.Teacher;
import com.april.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;

public class MyTest1 {
public static void main(String[] args) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
sqlSession.close();
}
}

StudentMapper.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">
<!--configuration:核心配置文件-->
<mapper namespace="com.april.mapper.StudentMapper">

<!--
思路:
1. 查询所有学生信息
2. 根据查询出来的学生tid,寻找对应的老师
-->

<!-- 这时候我们发现 直接用下列语句 会出现teacher=null的情况 -->
<!-- <select id="getStudent" resultType="Student">-->
<!-- select * from student-->
<!-- </select>-->

<select id="getStudent" resultMap="StudentAndTeacher">
select * from student
</select>
<!-- id为我们上面定义的resultMap Type为我们getStudent这个方法的返回值-->
<resultMap id="StudentAndTeacher" type="Student">
<!--学生表中的ID-->
<result property="id" column="id"></result>
<!--学生表中的name-->
<result property="name" column="name"></result>
<!--
由于我们Student实体类中写了一个Teacher的对象,这个对象中有多个字段,我们将它理解为一个复杂的属性
复杂的属性,我们需要单独的进行处理 其中对象的处理方式为:association 集合的处理方式为:collection
这里的property="teacher"对应了我们Student实体类中 private Teacher teacher; 的teacher
column="tid" 是调用了getTeacher中的参数 因为tid是外键,会自动关联到id
所以这里填id是可以的, 为了方便查看你也可以将下面的#{id}改为#{tid}
javaType="Teacher" 是我们java中进行的返回数据类型 private Teacher teacher; 这里的数据类型是Teacher
select="getTeacher" 就是调用了下方getTeacher的这个select标签
-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"></association>
</resultMap>

<select id="getTeacher" resultType="Teacher">
select * from mybatis.teacher where id = #{id}
</select>
</mapper>

11.3.2、按照结果嵌套处理

StudentMapper.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">
<!--configuration:核心配置文件-->
<mapper namespace="com.april.mapper.StudentMapper">
<select id="getStudent" resultMap="StudentAndTeacher">
select s.id as sid,s.name as sname,t.id as tid,t.name as tname
from student s,teacher t
where s.tid = t.id
</select>

<resultMap id="StudentAndTeacher" type="Student">
<!--由于我们之前没有取别名 所以result中的column都是取的数据库本来的名字,我们现在取了别名之后 对应的列就要为别名的名称-->
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
<!--
property="teacher" 还是对应了我们Student实体类中 private Teacher teacher; 的teacher
javaType="Teacher" 也还是我们java中进行的返回数据类型 private Teacher teacher; 这里的数据类型是Teacher
在里面添加result是因为我们对他Teacher的这个对象 进行了属性的赋值
id 就是我们查询语句中的tid name就是我们查询语句中的tname
-->
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"></result>
<result property="name" column="tname"></result>
</association>
</resultMap>

</mapper>

测试查询还是一样的代码

import com.april.mapper.TeacherMapper;
import com.april.pojo.Teacher;
import com.april.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;

public class MyTest1 {
public static void main(String[] args) {
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
sqlSession.close();
}
}

回顾Mysql多对一查询方式

  • 子查询

  • 连表查询

12、一对多处理

比如:一个老师拥有多个学生!对于老师而言,就是一对多的关系

12.1、测试环境搭建

新建一个项目,将多对一写的代码清空,并修改实体类代码:

Student.java

package com.april.pojo;

public class Student {
private Integer id;
private String name;
private int tid;

public Student() {
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getTid() {
return tid;
}

public void setTid(int tid) {
this.tid = tid;
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", tid=" + tid +
'}';
}
}

Teacher.java

package com.april.pojo;

import java.util.List;

public class Teacher {
private Integer id;
private String name;
private List<Student> students;

public Teacher() {
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public List<Student> getStudents() {
return students;
}

public void setStudents(List<Student> students) {
this.students = students;
}

@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
", students=" + students +
'}';
}
}

由于我这里使用的是一对多,也就是一个老师对应多个学生,那么我们将原本属于学生的List<Teacher>改为属于老师的List<Student>

12.2、嵌套处理

12.2.1、按照查询嵌套处理

TeacherMapper.xml(第一种getTeacher是student为null的错误示范):

<?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">
<!--configuration:核心配置文件-->
<mapper namespace="com.april.mapper.TeacherMapper">
<select id="getTeacher" resultType="Teacher">
select * from teacher
</select>
<select id="getTeacherAndStudent" resultMap="TeacherAndStudent">
select s.id sid,s.name sname,
t.name tname,t.id tid
from student s,teacher t
where s.tid = t.id
and t.id = #{tid}
</select>

<resultMap id="TeacherAndStudent" type="Teacher">
<result property="id" column="tid"></result>
<result property="name" column="tname"></result>
<!--
由于我们的pojo实体类中使用的是集合,所以这里使用collection property用的就是实体类中的属性名
javaType:指定属性的类型
集合中的泛型信息,我们要使用ofType获取,实际上并无太大区别,只不过集合必须要用ofType
但是在collection里面写的内容和association是一致的
-->
<collection property="students" ofType="Student">
<result property="id" column="sid"></result>
<result property="name" column="sname"></result>
<result property="tid" column="tid"></result>
</collection>
</resultMap>
</mapper>

TeacherMapper.java:

package com.april.mapper;


import com.april.pojo.Teacher;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface TeacherMapper {
// 获取老师
List<Teacher> getTeacher();

// 获取指定老师下的所有学生及老师的信息
Teacher getTeacherAndStudent(@Param("tid") int id);
}

Test:

import com.april.mapper.TeacherMapper;
import com.april.pojo.Teacher;
import com.april.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class MyTest1 {
@Test
public void Test1(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> teacherList = mapper.getTeacher();
for (Teacher teacher : teacherList) {
System.out.println(teacher);
}
sqlSession.close();
}

@Test
public void Test2(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherAndStudent(1);
System.out.println(teacher);
sqlSession.close();
}
}

12.2.2、按照结果嵌套处理

TeacherMapper.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">
<!--configuration:核心配置文件-->
<mapper namespace="com.april.mapper.TeacherMapper">
<!-- 查询老师表 -->
<select id="getTeacherAndStudent" resultMap="TeacherAndStudent">
select * from teacher where id = #{tid}
</select>

<!-- 根据老师的ID去查询学生 -->
<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid = #{tid}
</select>

<!-- 返回的结果
collection:代表集合
property:代表Teacher中的列
javaType:指定pojo中属性的类型
ofType:映射到list集合属性中pojo的类型
select:调用嵌套的标签id
column:resultMap中返回的Type中类型的字段
-->
<resultMap id="TeacherAndStudent" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="tid"></collection>
</resultMap>


</mapper>

TeacherMapper.java:

package com.april.mapper;


import com.april.pojo.Teacher;
import org.apache.ibatis.annotations.Param;


public interface TeacherMapper {
// 获取指定老师下的所有学生及老师的信息
Teacher getTeacherAndStudent(@Param("tid") int id);
}

MyTest.java:

import com.april.mapper.TeacherMapper;
import com.april.pojo.Teacher;
import com.april.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class MyTest1 {
   @Test
   public void Test(){
       SqlSession sqlSession = MybatisUtil.getSqlSession();
       TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
       Teacher teacher = mapper.getTeacherAndStudent(1);
       System.out.println(teacher);
       sqlSession.close();
  }
}

12.3、小结

  • 关联 - association 【多对一】

  • 集合 - collection 【一对多】

  • javaType&ofType

    • javaType 用来指定实体类中属性的类型

    • ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型

注意点

  • 保证SQL的可读性,尽量保证通俗易懂

  • 注意一对多和多对一中,属性名和字段的问题

  • 如果不好排查错误 可以使用日志来进行解决

慢SQL

例如说,你和A需要查询出同一段数据,而A写出来的SQL语句进行查询的时候只需要1秒,而你查询的时候需要100秒甚至更多

因为我们现在写代码只是查询出一点点数据,如果数据像正式开发环境一样,数据开始变多,就会存在这样的情况,我们这个SQL没加任何优化,对于公司来说就是有问题的代码,所以自己写SQL的风险还是比较高,我们可以学习到一些思路之后,把一些东西总结到自己的文档/博客等里面;可以把这些代码作为一个参考,来写你们工作中的代码

  • SQL高频面试题

    • Mysql引擎(InnoDB和MyISAM)

    • InnoDB底层原理

    • 索引,索引优化

13、动态SQL

13.1、什么是动态SQL

什么是动态SQL

动态SQL指的是根据不同的条件来生成不同的SQL语句

官方解释:

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if

  • choose (when, otherwise)

  • trim (where, set)

  • foreach

13.2、搭建环境

1 创建博客表

CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8

2 创建一个基础工程

  1. 导包

    新建一个MybatisTest8的java项目

  2. 编写配置文件

    将resources的db.propertiesmybatis-config.xml,以及utils下面的MybatisUtils.java拷贝过来(mybatis-config.xml中的mappers标签里面记得修改一下)

  3. 编写实体类(记得加上get/set方法)

package com.april.pojo;

import java.util.Date;

public class Blog {
   // 用户ID
   private String id;
   // 标题
   private String title;
   // 作者
   private String author;
   // 创建时间
   private Date createTime; // sql中为create_time
   // 浏览量
   private Integer views;
}
  1. 编写实体类对应的Mapper接口和Mapper.xml

BlogMapper.java[在java目录的com.april.mapper下]:

package com.april.mapper;

public interface BlogMapper {
}

BlogMapper.xml[在resources目录的com.april.mapper下]:

<?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">
<!--configuration:核心配置文件-->
<mapper namespace="com.april.mapper.BlogMapper">

</mapper>
  1. 添加一个获取uuid的工具类

package com.april.utils;

import org.junit.Test;

import java.util.UUID;

public class IDUtils {

   /**
    * @Author 四月一
    * @return UUID
    * @Description 获得一个UUID
    * 因为部分企业中有这样一个应用,他们的ID不是那种123456789这种,他们是一个随机的UUID,为了保证唯一和随机
    */
   public static String getID(){
       return UUID.randomUUID().toString().replaceAll("-","");
  }

   @Test
   public void Test(){
       String id = getID();
       System.out.println(id);
  }

}

由于我们pojo类中的时间名字为createTime,而数据库中的为create_time,这种带下划线的我们在java中要改为驼峰命名法,要解决这种驼峰命名和数据库字段不一致的问题我们只需要在mybatis-config.xml中多添加一个设置即可;在settings的标签下添加一行:<setting name="mapUnderscoreToCamelCase" value="true"/>

现在我们的数据库中是没有数据的,所以我们创建一个添加数据的方法(为了方便,我就不加上文件名了,自行理解一下):

// 插入数据
   int addBlog(Blog blog);
<insert id="addBlog" parameterType="Blog">
      insert into blog(id,title,author,create_time,views)
      values (#{id},#{title},#{author},#{createTime},#{views});
   </insert>
@Test
   public void MyTest1(){
       SqlSession sqlSession = MybatisUtil.getSqlSession();
       BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
       Blog blog = new Blog();
       blog.setId(IDUtils.getID());
       blog.setTitle("Mybatis");
       blog.setAuthor("April-1");
       blog.setCreateTime(new Date());
       blog.setViews(9999);
       mapper.addBlog(blog);

       blog.setId(IDUtils.getID());
       blog.setTitle("Java");
       mapper.addBlog(blog);

       blog.setId(IDUtils.getID());
       blog.setTitle("Spring");
       mapper.addBlog(blog);

       blog.setId(IDUtils.getID());
       blog.setTitle("微服务");
       mapper.addBlog(blog);

       blog.setAuthor("四月一");
       blog.setTitle("MySql");
       blog.setViews(456789);
       mapper.addBlog(blog);

       blog.setTitle("SpringMvc");
       blog.setViews(1000);
       mapper.addBlog(blog);

       blog.setTitle("SpringCloud");
       blog.setViews(3000);
       mapper.addBlog(blog);

       sqlSession.close();
  }

13.3、IF

官方解释:

需要实现:如果不传递参数 则直接查询所有 如果传递参数 则根据传递的参数查询

代码如下:

// 查询博客(通过IF判断)
   List<Blog> queryBlogIf(Map map);
    <select id="queryBlogIf" parameterType="map" resultType="Blog">
      select * from blog where true
       <if test="title != null">
          and title = #{title}
       </if>
       <if test="author != null">
          and author = #{author}
       </if>
   </select>
@Test
   public void MyTest2(){
       SqlSession sqlSession = MybatisUtil.getSqlSession();
       BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
       HashMap map = new HashMap();
//       map.put("title","Mybatis");
       map.put("author","April-1");
       List<Blog> blogs = mapper.queryBlogIf(map);
       for (Blog blog : blogs) {
           System.out.println(blog);
      }
  }

13.4、trim(where,set)

13.4.1、where

我们在13.3中可以看到 我们的where默认写成了一个where true,不然我们的and会无法使用 毕竟我们的sql语句不能直接拼接and,如果直接用where true的话,我们是不好防止SQL注入的,针对这个情况,我们就可以使用where标签。

例如说,我们刚刚写的语句是这样的:

 <select id="queryBlogIf" parameterType="map" resultType="Blog">
      select * from blog where true
       <if test="title != null">
          and title = #{title}
       </if>
       <if test="author != null">
          and author = #{author}
       </if>
   </select>

现在我们可以改成这样:

<select id="queryBlogIf" parameterType="map" resultType="Blog">
      select * from blog
       <where>
           <if test="title != null">
              and title = #{title}
           </if>
           <if test="author != null">
              and author = #{author}
           </if>
       </where>
   </select>

对于这个 官方是这么解释的:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

13.4.2、set

我们如果修改数据的时候,我们set标签每set一条数据后面就要加上逗号,如果要转换成动态,我们的逗号就要自动去除,针对这个情况,我们就可以使用set标签。

演示一下代码:

// 更新博客
   int updateBlog(Map map);
<update id="updateBlog" parameterType="map">
      update blog
       <set>
           <if test="title != null">
              title = #{title},
           </if>
           <if test="author != null">
              author = #{author},
           </if>
       </set>
      where id = #{id}
   </update>
 @Test
   public void MyTest4(){
       SqlSession sqlSession = MybatisUtil.getSqlSession();
       BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
       HashMap map = new HashMap();
       map.put("author","April-1");
       map.put("id","bbc166924f9e4256bfcf3e4c016eff4d");
       int i = mapper.updateBlog(map);
       if(i>0){
           System.out.println("修改成功!");
      }
       sqlSession.close();
  }

这里我们可以看到,我们的set标签里面是都带有逗号的,但是这些代码却能运行成功,原因是set标签会删除无关的逗号,官方说明如下:

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)

13.4.3、trim

我们的trim元素相当于是set和where的集合体,都可以使用,还可以进行自定义

例如说我们的where标签是这样:

<select id="queryBlogIf" parameterType="map" resultType="Blog">
      select * from blog
       <where>
           <if test="title != null">
              and title = #{title}
           </if>
           <if test="author != null">
              and author = #{author}
           </if>
       </where>
   </select>

我们就可以修改成这样:

 <select id="queryBlogIf" parameterType="map" resultType="Blog">
      select * from blog
       <trim prefix="WHERE" prefixOverrides="AND |OR ">
           <if test="title != null">
              and title = #{title}
           </if>
           <if test="author != null">
              and author = #{author}
           </if>
       </trim>
   </select>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

我们的set标签是这样:

<update id="updateBlog" parameterType="map">
      update blog
       <set>
           <if test="title != null">
              title = #{title},
           </if>
           <if test="author != null">
              author = #{author},
           </if>
       </set>
      where id = #{id}
   </update>

我们就可以修改成这样:

<update id="updateBlog" parameterType="map">
      update blog
       <trim prefix="SET" suffixOverrides=",">
           <if test="title != null">
              title = #{title},
           </if>
           <if test="author != null">
              author = #{author},
           </if>
       </trim>
      where id = #{id}
   </update>

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

具体的详情可以通过下面的表来看:

属性描述
prefix 给sql语句拼接的前缀
suffix 给sql语句拼接的后缀
prefixOverrides 去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND"
suffixOverrides 去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定

13.5、choose(when,otherwise)

官方解释:

需要实现:如果不传递参数 则返回符合条件的数据 如果传递参数 则根据传递的参数查询

    // 查询博客(通过choose判断)
   List<Blog> queryBlogChoose(Map map);
<select id="queryBlogChoose" parameterType="map" resultType="blog">
      select * from blog
       <where>
           <choose>
               <when test="title != null">
                  and title = #{title}
               </when>
               <when test="author != null">
                  and author = #{author}
               </when>
               <otherwise>
                  and views > 0
               </otherwise>
           </choose>
       </where>
   </select>
 @Test
   public void MyTest3(){
       SqlSession sqlSession = MybatisUtil.getSqlSession();
       BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
       HashMap map = new HashMap();
       map.put("author","April-1");
       List<Blog> blogs = mapper.queryBlogChoose(map);
       for (Blog blog : blogs) {
           System.out.println(blog);
      }
  }

由于choose标签的特殊性,等同于java中的switch,所以他不管传递多少个参数,都只会返回出一个结果,具体的详情我们可以看控制台输出的日志文件(会输出SQL语句)

13.6、SQL片段

有的时候,我们可能会将一些公共的部分抽取出来,方便复用

例如说,我们刚刚的

  <if test="title != null">
              title = #{title},
           </if>
           <if test="author != null">
              author = #{author},
           </if>

这一段,我们使用了多次,我们就可以把他封装成一个sql片段,从而导入使用

<sql id="if-title-author">
       <if test="title != null">
          and title = #{title}
       </if>
       <if test="author != null">
          and author = #{author}
       </if>
   </sql>


<select id="queryBlogIf" parameterType="map" resultType="Blog">
      select * from blog
       <trim prefix="WHERE" prefixOverrides="AND |OR ">
          <include refid="if-title-author"></include>
       </trim>
   </select>

像这样就可以啦!

使用方式

  1. 使用SQL标签抽取公共部分

  2. 在需要使用的地方 使用include标签引用即可

注意事项

  • 最好基于单表来定义SQL片段

  • 不要存在where标签

13.7、foreach

我们需要实现如下功能:

select * from user where id in (1,2,3);

代码如下:

    // 查询博客(通过Foreach)
   List<Blog> queryBlogForeach(Map map);
<!--
      collection:集合 需要从Map里面传递
      item:从这个集合中遍历出来的每一项(集合项)
      index:索引
      open:在开头字符串放置分隔符
      close:在结尾字符串放置分隔符
      separator:在foreach每循环一次之后放置分隔符
   -->
   <select id="queryBlogForeach" parameterType="map" resultType="Blog">
      select * from blog
        <where>
            id in
            <foreach collection="list" item="id" index="index" open="(" separator="," close=")">
                #{id}
            </foreach>
        </where>
   </select>
 @Test
   public void MyTest5(){
       SqlSession sqlSession = MybatisUtil.getSqlSession();
       BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
       HashMap map = new HashMap();
       ArrayList<String> list = new ArrayList<String>();
       list.add("bbc166924f9e4256bfcf3e4c016eff4d");
       list.add("62419632ed2d4cdab64386b74f49432a");
       map.put("list",list);
       List<Blog> blogs = mapper.queryBlogForeach(map);
       for (Blog blog : blogs) {
           System.out.println(blog);
      }
       sqlSession.close();
  }

13.8、小结

其实动态SQL就是在拼接我们的SQL语句,我们只需要保证SQL的正确性,按照SQL的方式,去排列组合就可以了

建议

  • 先在mysql中写出完整的SQL,再对应的去修改成我们的动态SQL来实现通用

14、缓存

14.1、简介

  1. 什么是缓存?[cache]

    • 存在内存中的临时数据

    • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题

  2. 为什么要使用缓存?

    • 减少和数据库的交互次数,减少系统开销,提高系统效率。

  3. 什么样的数据能够使用缓存?

    • 经常查询并且不经常改变的数据。

14.2、Mybatis缓存

  • Mybatis缓存包含一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存。缓存可以极大的提升查询效率。

  • Mybatis系统中默认定义了两种缓存:一级缓存和二级缓存

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也成为本地缓存)

    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

    • 为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

14.3、一级缓存

介绍

我们的Sqlsession只在开启和sqlsession.close()之间有效,离开之后就无效了

  • 一级缓存也叫本地缓存:

    • 与数据库同一次回话期间查询到的数据会放到本地缓存中

    • 如果以后需要获取相同的数据,直接从缓存中拿,没有必要重新查询数据库

测试步骤

  1. 开启日志

  2. 测试在一个Session中查询两次记录

  3. 查看日志输出

User queryUserByID(@Param("id") int id);
<select id="queryUserByID" resultType="User">
      select * from user where id = #{id}
   </select>
 @Test
   public void test(){
       SqlSession sqlSession = MybatisUtil.getSqlSession(); // 一级缓存开始
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       User user = mapper.queryUserByID(1);
       System.out.println(user);
       System.out.println("======================");
       User user2 = mapper.queryUserByID(1);
       System.out.println(user2);
       System.out.println(user == user2);
       sqlSession.close(); // 一级缓存结束
  }

这里我们可以看到 从开启到结束 我们只进行了一次SQL查询 查出了两个对象 说明了我们另外一个结果是从缓存中进行读取的;

缓存失效的情况:

  1. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存

  2. 查询不同的东西

  3. 查询不同的Mapper

  4. 手动清理缓存

小结

一级缓存是默认开启的,只在一次Sqlsession中有效,也就是拿到连接到关闭连接这个区间段!

14.4、二级缓存

介绍

  • 二级缓存也叫全局缓存,一级缓存作用于太低了,所以诞生了二级缓存;

  • 基于namespace级别的缓存,一个命名空间,对应一个二级缓存

  • 工作机制

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

    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,以及缓存中的数据被保存到二级缓存中;

    • 新的会话查询信息,就可以从二级缓存中获取内容;

    • 不同的mapper查询出的数据会放在自己对应的缓存中;

如何开启二级缓存

  1. 开启全局缓存

    mybatis-config.xmlsesstings标签中输入如下内容:

                <!--开启全局缓存-->
               <setting name="cacheEnabled" value="true"/>
  2. 在要使用二级缓存的Mapper.xml中添加标签<cache/>(这个只适用于不定制任何功能 我下面的代码是带定制的 详情看注释)

        <!--
           在当前Mapper.xml使用二级缓存
           eviction: 代表清除策略
             总共四个清除策略:
               LRU – 最近最少使用:移除最长时间不被使用的对象。
               FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
               SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
               WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
           flushInterval:代表间隔多少秒刷新
           size:代表最多存储多少个引用
           readonly:为true则标识只读
       -->
       <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
       <!--useCache="true"代表使用二级缓存 useCache="false"代表不使用二级缓存-->
       <select id="queryUserByID" resultType="User" useCache="true">
          select * from user where id = #{id}
       </select>
  3. 这里我们启用缓存之后,发现不同的连接调用的也是同一个缓存了

测试可能出现的问题

pojo类的实体类记得序列化一下,不然可能会在使用的时候报错!

public class User implements Serializable {
   private Integer id;
   private String name;
   private String pwd;
   private String birthday;
}

小结

  • 只要开启了二级缓存,在同一个Mapper下就有效

  • 所有的数据都会先放在一级缓存中;

  • 只有当会话提交/会话关闭的时候,才会提交到二级缓存中;

14.5、缓存原理

14.6、自定义缓存-ehcache

这里可以自己选择跳过,因为目前的主流缓存容器是redis,这里只做简单的使用讲解,实际使用与其他的并无太大区别

  1. 导包

     <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.1.0</version>
    </dependency>
  2. 调用(Mapper.xml)

        <!--调用EhcacheCache缓存-->
       <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  3. 配置文件(resouse目录下ehcache.xml)

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
            updateCheck="false">
       <!--
          diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
          user.home – 用户主目录
          user.dir – 用户当前工作目录
          java.io.tmpdir – 默认临时文件路径
        -->
       <diskStore path="./tmpdir/Tmp_EhCache"/>
       <defaultCache
               eternal="false"
               maxElementsInMemory="10000"
               overflowToDisk="false"
               diskPersistent="false"
               timeToIdleSeconds="1800"
               timeToLiveSeconds="259200"
               memoryStoreEvictionPolicy="LRU"/>

       <cache
               name="cloud_user"
               eternal="false"
               maxElementsInMemory="5000"
               overflowToDisk="false"
               diskPersistent="false"
               timeToIdleSeconds="1800"
               timeToLiveSeconds="1800"
               memoryStoreEvictionPolicy="LRU"/>
       <!--
         defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
       -->
       <!--
         name:缓存名称。
         maxElementsInMemory:缓存最大数目
         maxElementsOnDisk:硬盘最大缓存个数。
         eternal:对象是否永久有效,一但设置了,timeout将不起作用。
         overflowToDisk:是否保存到磁盘,当系统宕机时
         timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
         timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
         diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
         diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
         diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
         memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
         clearOnFlush:内存数量最大时是否清除。
         memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
         FIFO,first in first out,这个是大家最熟的,先进先出。
         LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
         LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
      -->

    </ehcache>

由于我们缓存目前会用Redis来做缓存,而这个缓存效率比较低,所以也只是给大家教一下怎么调用自定义缓存;

 

posted @   April-四月一  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示