0%

Mybatis

maven仓库

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>

持久层:持久化就是将程序的数据在瞬时状态和持久状态转化的过程,一般是数据库,而持久层就是完成持久化的代码块

特点:层界限十分明显

为什么需要Mybatis?

  • 方便,帮助程序将数据存入数据库中
  • JDBC代码太复杂,简化,框架,自动化
  • sql与代码分离,提高可维护性
  • 提供映射标签,支持对象与数据库orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组件维护
  • 提供xml标签,支持动态编写sql

开始Mybatis

  1. 在resources目录下创建mybatis-config.xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?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?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
    </dataSource>
    </environment>
    </environments>
    <mappers>
    <mapper resource="com/lan5th/dao/UserMapper.xml"/>
    </mappers>
    </configuration>

    mappers标签用于注册实现类的mapper.xml

  2. 编写工具类MybatisUtils

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
    try {
    //获取sqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    } catch (IOException e){
    e.printStackTrace();
    }
    }

    public static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession();
    }
    }

    在调用sqlSessionFactory.openSession()时若添加boolean参数为true则可以设置事务自动提交

  3. 编写实体类User

  4. 编写接口UserMapper(代替Dao接口)

    1
    2
    3
    public interface UserMapper {
    List<User> getUserList();
    }
  5. 编写UserMapper.xml配置文件(代替DaoImpl实现类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?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.lan5th.dao.UserMapper">
    <select id="getUserList" resultType="com.lan5th.pojo.User">
    select * from `user`;
    </select>
    </mapper>
  6. 编写测试类UserMapperTest.java(使用junit)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class UserMapperTest {

    @Test
    public void test(){
    //第一步,获取SqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    //方法一:getMapper
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = userMapper.getUserList();

    //方法二(已过时):
    //List<User> userList = sqlSession.selectList("com.lan5th.dao.UserMapper.getUserList");

    for (User user : userList) {
    System.out.println(user.toString());
    }

    sqlSession.close();
    }

    }

无法找到Mapper.xml解决办法:在pom.xml中添加

1
2
3
4
5
6
7
8
9
10
11
12
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>

CRUD

namespace

namespace中的包名要和Dao/mapper中的包名一致

  • id:对应的namespace中的方法名
  • resultType:Sql语句执行的返回值
  • parameterType:函数传入的参数类型(可以通过#{}直接拿到对象中的属性)

增删改查

增加接口函数UserMapper.java

1
2
3
4
5
6
7
8
9
10
11
public interface UserMapper {
List<User> getUserList();

User getUser(int i);

int addUser(User u);

int updateUser(User u);

int deleteUser(int i);
}

修改配置文件UserMapper.xml

引用参数时可选${}或#{},但前者是基本的字符串拼接,无法防止sql注入,后者类似于预编译,可以防止大部分的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
<?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.lan5th.dao.UserMapper">
<select id="getUserList" resultType="com.lan5th.pojo.User">
select * from mybatis.user;
</select>

<select id="getUser" resultType="com.lan5th.pojo.User">
select * from mybatis.user where id = #{id};
</select>

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

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

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

测试类UserMapperTest.java

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
package com.lan5th.dao;

import com.lan5th.pojo.User;
import com.lan5th.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {

@Test
public void test() {
//第一步,获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();

//方法一:getMapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();

//方法二(已过时):
//List<User> userList = sqlSession.selectList("com.lan5th.dao.UserMapper.getUserList");

for (User user : userList) {
System.out.println(user.toString());
}

sqlSession.close();
}

@Test
public void selectTest() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

User user = mapper.getUser(1);
System.out.println(user);

sqlSession.close();
}

@Test
public void insertTest() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

int res = mapper.addUser(new User(4, "User4", "4444"));
if (res > 0)
System.out.println("插入成功");

sqlSession.commit();
sqlSession.close();
}

@Test
public void updateTest() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

int res = mapper.updateUser(new User(3, "updatedUser3", "3333"));
if (res > 0)
System.out.println("更新成功");

sqlSession.commit();
sqlSession.close();
}

@Test
public void deleteTest() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

int res = mapper.deleteUser(4);
if (res > 0)
System.out.println("删除成功");

sqlSession.commit();
sqlSession.close();
}
}

传递参数

  • 单个基本类型参数

    非对象,可以不写parameterType

    可以直接在sql中获取,如

    1
    select * from mybatis.user where id = #{id};
  • 多个参数

    基本思想封装成对象或使用map

    • 对象传递参数,parameterType=”Object(根据情况写具体类)”

      1
      insert into mybatis.user values (#{id},#{name},#{pwd});

      缺点:必须封装一个完整的对象,参数较多时不便

    • map传递参数,parameterType=”map”

      先给map添加键值对

      1
      2
      3
      4
      Map<String, Object> map = new HashMap<String, Object>();
      map.put("userId",1);
      map.put("userName","MapUser");
      int res = mapper.updateUserName(map);

      添加sql语句

      1
      2
      3
      <update id="updateUserName" parameterType="map">
      update mybatis.user set name=#{userName} where id=#{userId};
      </update>

拓展:模糊查询

sql语句

1
2
3
<select id="getUserLike" resultType="com.lan5th.pojo.User">
select * from mybatis.user where name like "%"#{value}"%";
</select>

但是如上格式可能会产生sql注入的问题,可改为在传入参数时就封装完毕

1
List<User> userList = mapper.getUserLike("%李%");
1
2
3
<select id="getUserLike" resultType="com.lan5th.pojo.User">
select * from mybatis.user where name like #{value};
</select>

配置

  • mybatis-config.xml
  • Mybatis配置文件包含了深深影响Mybatis行为的属性信息

注意:configration标签内部元素顺序是已经指定的,如properties必须在最上层等,configration下所有的标签都需要写在固定的位置

environments

Mybatis可以配置多种环境(尽管最终能够同时运行的只有一个),每个SqlSessionFactory实例只能选择一种环境,通过default选择需要使用的环境

image-20210402152641543

POOLED:连接池,提升sql执行效率(具体在MySql篇查看)

properties

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

这些属性都是可外部配置且可动态替换的,可以在典型的java属性文件中配置,亦可通过properties元素的子元素来传递

db.properties

1
2
3
4
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456

`mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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>
<!--配置文件标签-->
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<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/lan5th/dao/UserMapper.xml"/>
</mappers>
</configuration>

还可以在properties标签内配置属性

image-20210402154934191

如果同时在配置文件中的properties标签中和外部properties文件中同时配置了同一个属性,则会优先使用外部配置文件中该属性的值。

typeAliases

类型别名是为java类型设置一个短的名字,存在意义仅在于减少完全限定名的冗余

mybatis-config.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
<?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>
<properties resource="db.properties"/>
<!--别名标签-->
<typeAliases>
<typeAlias type="com.lan5th.pojo.User" alias="User"/>
</typeAliases>
<environments default="development">
<environment id="development">
<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/lan5th/dao/UserMapper.xml"/>
</mappers>
</configuration>

也可以制定一个包名,Mybatis会在包名下自动搜索需要的Java Bean

扫描实体类的包,他的默认别名就为这个类的类名,首字母小写

1
2
3
<typeAliases>
<package name="com.lan5th.pojo"/>
</typeAliases>

也可以通过给实体类添加注解来修改别名

1
2
3
4
@Alias("User")
public class User {
...
}

settings

关于mybatis的拓展功能设置

一个完整配置的settings元素实例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

每个选项的具体含义请到mybatis官网查看

其他配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins插件(仅列举出几个典型的)
    • mybatis-generator-core(可根据数据库自动生成增删改查代码)
    • mybatis-plus(与mybatis互补的简化工具)
    • 通用mapper

mappers

MapperRegistry:注册我们的Mapper文件

  • 方式一

    1
    2
    3
    <mappers>
    <mapper resource="com/lan5th/dao/UserMapper.xml"/>
    </mappers>
  • 方式二:使用class文件绑定注册

    1
    2
    3
    <mappers>
    <mapper class="com.lan5th.dao.UserMapper"/>
    </mappers>

    注意点:接口和接口的Mapper配置文件必须同名,也必须在同一文件夹下

  • 方式三:使用扫描包进行注入绑定,mybatis自动扫描包下的所有接

    1
    2
    3
    <mappers>
    <package name="com.lan5th.dao"/>
    </mappers>

    注意点与方式二相同

生命周期和作用域

image-20210402225136243

  • SqlSessionFactoryBuildert

    作为一个局部变量,一旦创建完SqlSessionFactory便不再需要

  • SqlSessionFactory

    可以理解为数据库连接池

    SqlSessionFactory一旦被创建就应该在运行期间一直存在,不能丢弃它或创建另一个实例,因此SqlSessionFactory的最佳作用域是应用作用域

    应使用单例模式或静态单例模式

  • SqlSession

    连接到连接池的一个请求

    SqlSession的实例不是线程安全的,因此不能被共享,最佳作用域是请求或方法作用域

    使用完成后需要立即关闭,否则资源会被占用

image-20210402231412062

其中每一个Mapper代表一个具体的业务

ResultMap

解决属性名和字段名不一致的问题

1
2
3
4
5
6
7
8
9
<resultMap id="UserMap" type="User">
<result column="idInDatabase" property="idInClass"/>
<result column="nameInDatabase" property="nameInClass"/>
<result column="passwordInDatabase" property="pwdInClass"/>
</resultMap>

<select id="getUserById" resultMap="UserMap">
select * from mybatis.user where id = #{id}
</select>

日志

logImpl

日志工厂

选项:

  • SLF4J
  • LOG4J
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING
  • NO_LOGGING

在配置文件mybatis-config.xml中添加

1
2
3
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

log4j

简单使用:需要导入log4j的maven包

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

resources目录下新建log4j.properties配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#将等级为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/mybatis.log
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{yy-MM-dd}][%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

除了log4j的默认输出,还可以在方法中手动添加需要记录的日志,如在UserDaoTest.java测试类中添加测试方法

1
2
3
4
5
public void testLog4j(){
Logger.info("info:testLog4j");
Logger.debug("debug:testLog4j");
Logger.error("error:testLog4j");
}

分页

limit实现

用sql语句

1
select * from mybatis.user limit 0,5;

其实位置为0,页面大小为5

1
2
3
<select id="getUserByRowBounds" parameterType="map" resultType="User">
select * from mybatis.user limit #{startIndex},#{pageSize};
</select>

测试类不再与上方代码类似,不再复述

高级分页

原因:mysql的limit会先将所有的limit2之前的数据加载到内存中,然后取需要的数据,造成内存浪费,这里子查询先取需要的id,加载到内存中的数据较少,由外层查询由in语句再进行查询;

注意点:mysql5.7之前不能在子句中使用limit条件,因此需要把子查询b2中的结果再封装成b3这张表,再从外层取id集合

1
2
3
<select id="getPagination" resultType="com.lan5th.blog.pojo.BlogDetail">
select * from lz_blog.blogs as b1 where b1.id in (select b3.id from (select b2.id from lz_blog.blogs as b2 where b2.deleted = 0 order by create_time desc limit #{preNum}, #{postNum}) as b3);
</select>

RowBounds实现

(近些年不再使用)

将所有数据查出缓存再将需要的取出显示

  1. 接口

    1
    List<User> getUserListByRowBounds();
  2. Mapper.xml

    1
    2
    3
    <select id="getUserByRowBounds" resultType="User">
    select * from mybatis.user;
    </select>
  3. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void getUserByRowBoundsTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    RowBounds rowBounds = new RowBounds(1, 2);

    List<User> userList = sqlSession.selectList("com.lan5th.dao.UserMapper.getUserByRowBounds", null, rowBounds);
    for (User user : userList) {
    System.out.println(user);
    }
    sqlSession.close();
    }

PageHelper插件

插件官网

通过注解开发

仅适用于一些简短的sql语句,且不能使用结果集映射

  • 接口类UserMapper.java

    1
    2
    3
    4
    public interface UserMapper {
    @select("select * from user")
    List<User> getUserByRowBounds();
    }
  • 配置文件mybatis-config.xml(注意是接口类,而不是xml配置文件)

    1
    2
    3
    <mappers>
    <mapper class="com.lan5th.dao.UserMapper"/>
    </mappers>

本质:反射机制

底层:动态代理

CRUD

image-20210410162230758

关于@Param()注解

  • 基本类型的参数或String类型需要添加
  • 引用类型不需要添加
  • 如果只有一个基本类型可以忽略但按规范一般都需要添加
  • 在sql语句中引用的就是@Param()中设定好的属性名

Mybatis具体执行过程

  1. Resources获取加载全局配置文件
  2. 实例化SqlSessionFactoryBuilder构造器
  3. 解析配置文件流XMLConfigBuilder
  4. Configration包含所有的配置信息
  5. SqlSessionFactory实例化
  6. transaction事务管理
  7. 创建executer执行器
  8. 创建sqlsession
  9. 实现CRUD,失败则回滚到6事务管理
  10. 查看是否执行成功,失败则回滚到6事务管理
  11. 提交事务
  12. 关闭

Lombok

  • idea中安装插件

  • 导入jar包(maven)

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    </dependency>

可用注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
@ExtensionMethod (Experimental, activate manually in plugin settings)

常用注解

1
2
3
4
5
@Data:包含无参构造、get、set、toString、hashCode、equals
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString

多对一

一个老师有多个学生

  • 对于学生而言,关联,多个学生关联一个老师【多对一】

  • 对于老师而言,集合,一个老师有很多学生【一对多】

  • 创建数据表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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');
  • 对象接口

    • StudentMapper
  • TeacherMapper

  • xml配置文件

    • StudentMapper.xml
  • TeacherMapper.xml

多对一

实体类

  • Student.java

    1
    2
    3
    4
    5
    6
    @Data
    public class Student {
    private int id;
    private String name;
    private Teacher teacher;
    }
  • Teacher.java

    1
    2
    3
    4
    5
    @Data
    public class Teacher {
    private int id;
    private String name;
    }

按照查询嵌套

类似子查询

StudentMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--查询所有学生信息,根据查询出的tid查询对应的老师-->
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂的属性需要单独处理对象 关联:association 集合:collection-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>

<select id="getStudent" resultMap="StudentTeacher">
select * from student where id = #{id};
</select>
<!--这里的参数名可以任意给定,但一般与上方association中的参数名相同-->
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{tid};
</select>

按照结果嵌套

类似联表查询

StudentMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>

<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid = t.id;
</select>

一对多

实体类

  • Student.java

    1
    2
    3
    4
    5
    6
    @Data
    public class Student {
    private int id;
    private String name;
    private int tid;
    }
  • Teacher.java

    1
    2
    3
    4
    5
    6
    @Data
    public class Teacher {
    private int id;
    private String name;
    private List<Student> students;
    }

按照查询嵌套处理

1
2
3
4
5
6
7
8
9
10
<resultMap id="TeacherStudent" type="Teacher">
<!--javaType:指定的属性类型 而集合中的泛型信息用ofType获取-->
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudent" column="id"/>
</resultMap>
<select id="getTeacher" resultMap="TeacherStudent">
select * from teacher where id = #{tid}
</select>
<select id="getStudent" resultType="Student">
select * from student where tid = #{id}
</select>

按照结果嵌套处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="TeacherStudent2" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
<select id="getTeacher2" resultMap="TeacherStudent2">
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>

注意点:

  • 保证SQL可读性,尽量保证通俗易懂
  • 注意一对多和多对一中属性名和字段问题
  • 问题不容易排查时使用日志

动态SQL

  • IF

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog where 1=1
    <if test="title != null">
    and title = #{title}
    </if>
    <if test="author != null">
    and author = #{author}
    </if>
    </select>
  • choose(when,otherwise)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <select id="queryBlogWhere" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <!--类似于java中的switch语句-->
    <where>
    <choose>
    <when test="title != null">
    title = #{title}
    </when>
    <when test="author != null">
    author = #{author}
    </when>
    <otherwise>
    view - #{view}
    </otherwise>
    </choose>
    </where>
    </select>
  • trim(where,set)

    只有当后面条件至少有一个满足时才会插入where或set

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <select id="queryBlogTrim" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <!--where语句当选择的第一项前有and/or时会自动去除-->
    <where>
    <if test="title != null">
    title = #{title}
    </if>
    <if test="author != null">
    and author = #{author}
    </if>
    </where>
    </select>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <update id="updateBlog" parameterType="map">
    update mybatis.blog
    <!--set语句当选择的最后一项后有','时会自动去除-->
    <set>
    <if test="title != null">
    title = #{title},
    </if>
    <if test="author != null">
    author = #{author}
    </if>
    </set>
    </update>
  • 还可以使用trim自定义选择

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <trim prefix="WHERE" prefixOverrides="AND">
    <if test="state != null">
    state = #{state}
    </if>
    <if test="title != null">
    AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
    AND author_name like #{author.name}
    </if>
    </trim>
    1
    2
    3
    <trim prefix="SET" suffixOverrides=",">
    ...
    </trim>
  • sql片段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--使用sql标签抽取公共部分
    最好根据单表来抽取sql
    不要包含where标签
    -->
    <sql id="if-title-author">
    <if test="title != null">
    title = #{title}
    </if>
    <if test="author !=null">
    author = #{author}
    </if>
    </sql>
    1
    2
    3
    4
    5
    6
    <select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
    <include refid="if=title-author"></include>
    </where>
    </select>
  • Foreach

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!--先传递一个map,其中包含键值对ids,内容为数组-->
    <select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from mybatis.blog
    <where>
    <foreach collection="ids" item="id" open="and (" close=")" separator="or">
    id = #{id}
    </foreach>
    </where>
    </select>

动态sql就是在拼接sql语句,我们只需保证sql的正确性,按照sql的格式去排列组合即可;一般先在对应的Dmms中写出完整的sql,在修改动态sql实现通用

缓存

image-20210414204042584

Mybatis缓存

image-20210414205433478

一级缓存

一级缓存也叫本地缓存,在mybatis中是默认开启的,只在一次SqlSession中有效

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中
  • 之后如果需要获取相同的数据,直接从缓存中拿到,不再查询数据库

缓存失效情况:

  • 查询不同的数据
  • 经过增删改操作之后,可能会改变原来的数据,缓存会刷新(如对data1进行修改,虽然没有改变data2数据,但data2数据的缓存也会被清空!)
  • 查询不同的Mapper.xml
  • 手动清理缓存sqlSession.clearCache()

二级缓存

二级缓存也叫全局缓存,基于namespace级别的缓存,一个名称空间对应一个二级缓存

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
  • 新的会话查询信息可以从二级缓存中获取内容
  • 不同的mapper查处的数据会放在自己对应的缓存中

开启步骤:

  1. 开启全局缓存(尽管是默认开启的)

    mybatis-config.xml

    1
    2
    3
    <settings>
    <setting name="cacheEnabled" value="true"/>
    </settings>
  2. 在某个Mapper中配置缓存

    UserMapper.xml

    1
    2
    3
    4
    5
    6
    <!--不使用参数可以直接血<cache/>-->
    <cache
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"/>

    使用FIFO算法清除缓存,每60s刷新,最多可以存储结果或对象的512个引用,且存储的对象为只读

  3. 测试问题:使用时需要将实体类序列化,否则会报错!

小结:

  • 开启二级缓存后,在同一Mapper下有效
  • 所有的数据都先放在一级缓存中,只有当会话提交或者关闭的时候才会提交到二级缓存中

mybatis-ehcache

pom.xml

1
2
3
4
5
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>

新建ehcache配置文件ehcache.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
<?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="java.io.tmpdir/Tmp_EhCache"/>
<!--
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,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<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"/>

</ehcache>

UserMapper.xml

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