Mybatis笔记

Mybatis

 

声明:本文是狂神说的Mybatis课程的笔记

 

【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂哔哩哔哩bilibili

 

 

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。

 

(2)如何获得Mybatis

  • 1 maven仓库
    2 
    3 <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    4 <dependency>
    5     <groupId>org.mybatis</groupId>
    6     <artifactId>mybatis</artifactId>
    7     <version>3.5.7</version>
    8 </dependency>
  • Github:GitHub - mybatis/mybatis-3

  • 中文文档:mybatis – MyBatis 3 | 简介

 

(3)持久化

数据持久化。

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

  • 内存:断电即失

  • 数据库(JDBC):io文件持久化

为什么需要持久化?

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

  • 内存贵

 

(4)持久层(Dao层)

  • 完成持久工作的代码块

  • 层界限十分明显

 

(5)为什么需要Mybatis

  • 方便将数据存入数据库

  • 传统的JDBC代码太复杂

  • 优点

    • 简单易学

    • 灵活

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

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

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

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

 

2、第一个Mybatis程序

(1)搭建环境

  ①创建数据库和表

CREATE DATABASE `mybatis`;
​
USE `mybatis`;
​
CREATE TABLE `user`(
    `id` INT(20) PRIMARY KEY,
    `name` VARCHAR(30) DEFAULT NULL,
    `pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
​
INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'admin','123456'),
(2,'user','123456'),
(3,'user2','123456');

 

  ②新建项目

    i.新建普通的maven项目

    ii.删除src目录

    iii.导入相关依赖

<!--导入依赖-->
<dependencies>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--Mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!--Junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.1</version>
    </dependency>
</dependencies>

 

(2)创建一个模块

  ①编写mybatis的核心配置文件

<?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=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="12345678"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

 

  ②编写mybatis的工具类

 1 //SqlSessionFactory → SqlSession
 2 public class MybatisUtil {
 3  4     private static SqlSessionFactory sqlSessionFactory;
 5  6     //获取SqlSessionFactory对象
 7     static {
 8         try {
 9             String resource = "mybatis-config.xml";
10             InputStream inputStream =  Resources.getResourceAsStream(resource);
11             sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
12         } catch (IOException e) {
13             e.printStackTrace();
14         }
15     }
16 17     //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例
18     //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
19 20     public static SqlSession getSqlSession(){
21         return sqlSessionFactory.openSession();
22     }
23 }

 

(3)编写代码

  ①实体类

 1 //实体类
 2 public class User {
 3  4     private int id;
 5     private String name;
 6     private String pwd;
 7  8     public User() {
 9     }
10 11     public User(int id, String name, String pwd) {
12         this.id = id;
13         this.name = name;
14         this.pwd = pwd;
15     }
16 17     public int getId() {
18         return id;
19     }
20 21     public void setId(int id) {
22         this.id = id;
23     }
24 25     public String getName() {
26         return name;
27     }
28 29     public void setName(String name) {
30         this.name = name;
31     }
32 33     public String getPwd() {
34         return pwd;
35     }
36 37     public void setPwd(String pwd) {
38         this.pwd = pwd;
39     }
40 }

 

  ②Dao接口

1 public interface UserDao {
2     List<User> getUserList();
3 }

 

  ③接口实现类由原来的UserDaoImpl转变为一个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">
<mapper namespace="com.my.dao.UserDao">
    <select id="getUserList" resultType="com.my.pojo.User">
        select * from user
    </select>
</mapper>

 

(4)测试

  JUnit测试。

 1 @Test
 2 public void test(){
 3     //1、获得SqlSession对象
 4     SqlSession sqlSession = MybatisUtil.getSqlSession();
 5  6     //方式一:getWrapper
 7     UserDao userDao = sqlSession.getMapper(UserDao.class);
 8     List<User> userList = userDao.getUserList();
 9     
10     //方式二
11     //List<User> userList = sqlSession.selectList("com.my.dao.UserDao.getUserList");
12 13     for(User user:userList){
14         System.out.println(user);
15     }
16 17     //关闭sqlSession
18     sqlSession.close();
19 }

 

注意:

  • org.apache.ibatis.binding.BindingException: Type interface com.my.dao.UserDao is not known to the MapperRegistry

    出现该异常是因为没有在mybatis-config.xml核心配置文件中将UserMapper.xml进行注册

  • java.lang.ExceptionInInitializerError

    Caused by: org.apache.ibatis.exceptions.PersistenceException:

    Error building SqlSession.

    The error may exist in com/my/dao/UserMapper.xml

    这是由于maven的约定大于配置,从而出现我们写的配置文件无法被导出或生效,解决方案是在pom.xml中进行如下配置:

    <!--在build中配置Resource来防止我们资源导出失败的问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
  • 出现1 字节的 UTF-8 序列的字节 1 无效问题

    解决1字节的UTF-8序列的字节1无效问题

 

3、CRUD

(1)namespace

  namespace中的包名要和 dao/mapper接口的包名一致。

 

(2)selelct

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

  • resultType:Sq|语句执行的返回值

  • parameterType :参数类型

  ①编写接口

1 //查询全部用户
2 List<User> getUserList();
3 //根据id查询用户
4 User getUserById(int id);

 

  ②编写对应的mapper中的sq|语句

<!--查询全部用户-->
<select id="getUserList" resultType="com.my.pojo.User">
    select * from user
</select>
<!--根据id查询用户-->
<select id="getUserById" parameterType="int" resultType="com.my.pojo.User">
    select * from user where id = #{id}
</select>

 

  ③测试

 1 @Test
 2 public void test(){
 3     //1、获得SqlSession对象
 4     SqlSession sqlSession = MybatisUtil.getSqlSession();
 5  6     UserDao userDao = sqlSession.getMapper(UserDao.class);
 7     List<User> userList = userDao.getUserList();
 8  9     for(User user:userList){
10         System.out.println(user);
11     }
12 13     sqlSession.close();
14 }
15 16 @Test
17 public void testSelect(){
18     SqlSession sqlSession = MybatisUtil.getSqlSession();
19     UserDao userDao = sqlSession.getMapper(UserDao.class);
20     User user = userDao.getUserById(1);
21     System.out.println(user);
22     sqlSession.close();
23 }

 

(3)insert

  ①编写接口

1 //插入数据
2 int insertUser(User user);

 

  ②编写对应的mapper中的sq|语句

<!--插入数据-->
<insert id="insertUser" parameterType="com.my.pojo.User">
    insert into user (id,name,pwd)
    values (#{id},#{name},#{pwd});
</insert>

 

  ③测试

 1 @Test
 2 public void testInsert(){
 3     SqlSession sqlSession = MybatisUtil.getSqlSession();
 4     UserDao userDao = sqlSession.getMapper(UserDao.class);
 5     int count = userDao.insertUser(new User(4,"zl","123456"));
 6     if(count>0){
 7         System.out.println("插入成功!");
 8     }
 9     //提交事务
10     sqlSession.commit();
11     sqlSession.close();
12 }
 

(4)update

  ①编写接口

1 //更新数据
2 int updateUser(User user);

 

  ②编写对应的mapper中的sq|语句

<!--更新数据-->
<update id="updateUser" parameterType="com.my.pojo.User">
    update user
    set name = #{name},pwd = #{pwd}
    where id = #{id};
</update>

 

  ③测试

 1 @Test
 2 public void testUpdate(){
 3     SqlSession sqlSession = MybatisUtil.getSqlSession();
 4     UserDao userDao = sqlSession.getMapper(UserDao.class);
 5     int count = userDao.updateUser(new User(4,"wu","12345678"));
 6     if(count>0){
 7         System.out.println("更新成功!");
 8     }
 9     //提交事务
10     sqlSession.commit();
11     sqlSession.close();
12 }

 

(5)delete

  ①编写接口

1 //删除数据
2 int deleteUserById(int id);

 

  ②编写对应的mapper中的sq|语句

<!--删除数据-->
<delete id="deleteUserById" parameterType="int">
    delete from user where id = #{id}
</delete>

 

  ③测试

 1 @Test
 2 public void testDelete(){
 3     SqlSession sqlSession = MybatisUtil.getSqlSession();
 4     UserDao userDao = sqlSession.getMapper(UserDao.class);
 5     int count = userDao.deleteUserById(4);
 6     if(count>0){
 7         System.out.println("删除成功!");
 8     }
 9     //提交事务
10     sqlSession.commit();
11     sqlSession.close();
12 }
 

4、Map

如果我们的实体类或数据库中的表,字段或者参数过多,应当考虑使用Map或者注解。

  • Map传递参数,直接在sql中取出key即可

  • 对象传递参数,直接在sql中取对象的属性即可

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

1 //根据id查询用户
2 User getUserById2(Map<String,Object> map);
<!--根据id查询用户-->
<select id="getUserById2" parameterType="map" resultType="com.my.pojo.User">
    select * from user where id = #{userId} and name = #{userName}
</select>
 1 @Test
 2 public void testSelect2(){
 3     SqlSession sqlSession = MybatisUtil.getSqlSession();
 4     UserDao userDao = sqlSession.getMapper(UserDao.class);
 5     Map<String,Object> map = new HashMap<String,Object>();
 6     map.put("userId",1);
 7     map.put("userName","admin");
 8     User user = userDao.getUserById2(map);
 9     System.out.println(user);
10     sqlSession.close();
11 }

 

5、模糊查询

下面的方式都是为了防止sql注入。

  ①Java代码执行的时候,传递通配符% %

<!--模糊查询-->
<select id="getUserLike" parameterType="String" resultType="com.my.pojo.User">
    select * from user where name like #{value}
</select>
1 List<User> userList = userDao.getUserLike("%u%");
 

  ②在Sql拼接中使用通配符

<!--模糊查询-->
<select id="getUserLike" parameterType="String" resultType="com.my.pojo.User">
    select * from user where name like "%"#{value}"%"
</select>
1 List<User> userList = userDao.getUserLike("u");
 

  ③使用sql的concat函数

<!--模糊查询-->
<select id="getUserLike" parameterType="String" resultType="com.my.pojo.User">
    select * from user where name like concat('%',#{value},'%')
</select>
1 ​List<User> userList = userDao.getUserLike("u");

 

6、配置解析

(1)核心配置文件

  MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

 

(2)环境配置(environments)

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

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

  • Mybatis默认的事务管理器是JDBC,连接池:POOLED

  • 每个数据库对应一个 SqlSessionFactory 实例

<configuration>
    <environments default="test">
        <environment id="development">
            ...
        </environment>
        <environment id="test">
            ...
        </environment>
    </environments>
</configuration>

 

(3)属性(properties)

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

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

  ①编写db.properties配置文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8
username=root
password=12345678

 

  ②编写核心配置文件

<configuration>
    <!--引入外部配置文件-->
    <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="password" value="123"/>
    </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><!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
    <mappers>
        <mapper resource="com/my/dao/UserMapper.xml"/>
    </mappers>
</configuration>
  • 可以直接引入外部文件

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

  • 如果两个文件有同一个字段,优先使用外部配置文件的

 

(4)类型别名(typeAliases)

  • 类型别名可为 Java 类型设置一个缩写名字

  • 仅用于 XML 配置,意在降低冗余的全限定类名书写

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

  • 实体类较多的时候,使用第二种方式

  ①指定具体的实体类

<!--在核心配置文件mybatis-config.xml-->
<!--给实体类起别名-->
<typeAliases>
    <typeAlias type="com.my.pojo.User" alias="userEntity"/>
</typeAliases>
<!--在UserMapper.xml中使用-->
<select id="getUserList" resultType="userEntity">
    select * from user
</select>

 

  ②指定一个包名

  MyBatis 会在包名下面搜索需要的 Java Bean。在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。若有注解,则别名为其注解值。

<!--在核心配置文件mybatis-config.xml-->
<typeAliases>
    <package name="com.my.pojo"/>
</typeAliases>
<!--在UserMapper.xml中使用-->
<select id="getUserList" resultType="user">
    select * from user
</select>
 
1 <!--存在@Alias注解-->
2 @Alias("Users")
3 public class User {}
<!--在UserMapper.xml中使用-->
<select id="getUserList" resultType="Users">
    select * from user
</select>

 

(5)设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

比较重要的几个设置:

设置名描述有效值默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true |false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true |false false
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J |LOG4J |LOG4J2 |JDK_LOGGING |COMMONS_LOGGING |STDOUT_LOGGING |NO_LOGGING 未设置
<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="logImpl" value="LOG4J2"/>
  <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>

 

(6)插件(plugins)

  • mybatis-generator-core

  • mybatis-plus

  • 通用mapper

 

(7)映射器(mappers)

通过映射器(mappers)告诉 MyBatis 到哪里去找到这些SQL 映射语句。

  ①方式一:使用相对于类路径的资源引用【推荐】

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

 

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

    注意点:

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

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

<mappers>
    <mapper class="com.my.dao.UserMapper"/>
</mappers>

 

  ③方式三:使用扫描包进行注册绑定

    注意点同方式二一样

<mappers>
    <package name="com.my.dao"/>
</mappers>

 

7、作用域(Scope)和生命周期

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

(1)SqlSessionFactoryBuilder

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

  • 实例的最佳作用域是方法作用域(也就是局部方法变量)

 

(2)SqlSessionFactory

  • 可以想象为:数据库连接池

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

  • SqlSessionFactory 的最佳作用域是应用作用域

  • 最简单的就是使用单例模式或者静态单例模式

 

(3)SqlSession

  • 每个线程都应该有它自己的 SqlSession 实例

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

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

 

8、ResultMap结果集映射

  作用是为了解决属性名和字段名不一致的问题。

(1)问题

  ①实体类的属性

1 public class User {
2     private int id;
3     private String name;
4     private String password;
5 }

 

  ②表的字段名

  ③查询结果

  ④不灵活的解决方法,给字段起别名

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

 

(2)resultMap

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

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

  • 即使没有显示配置resultMap,MyBatis也 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上

  • ResultMap 的优秀之处—你完全可以不用显式地配置它们

<!--结果集映射-->
<resultMap id="UserMap" type="com.my.pojo.User">
    <!--column数据库字段名,property实体类属性名-->
    <result column="pwd" property="password"/>
</resultMap>

<select id="getUserById" parameterType="int" resultMap="UserMap">
    select id,name,pwd from user where id = #{id}
</select>

 

9、日志

(1)日志工厂

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

 

(2)STDOUT_LOGGING标准日志输出

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

 

(3)Log4j

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

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

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

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

  ①导入logj4的包

<!--Log4j-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

 

  ②log4j.properties

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

#控制台输出的相关设置
1og4j.appender.console = org.apache.1og4j.ConsoleAppender
1og4j.appender.console.Target = System.out
1og4j.appender.console.Threshold=DEBUG
1og4j.appender.console.1ayout = org.apache.1og4j.PatternLayout
1og4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
1og4j.appender.file = org.apache.log4j.RollingFileAppender
1og4j.appender.file.File=./1og/my.1og
1og4j.appender.file.MaxFileSize=10mb
1og4j.appender.file.Threshold=DEBUG
1og4j.appender.file.layout=org.apache.1og4j.PatternLayout
1og4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
1og4j.logger.org.mybatis=DEBUG
1og4j.logger.java.sq1=DEBUG
1og4j.logger.java.sq1.Statement=DEBUG
1og4j.logger.java.sq1.ResultSet=DEBUG
1og4j.logger.java.sq1.PreparedStatement=DEBUG

 

  ③配置log4j为日志

<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

 

  ④测试

 

(4)简单使用

  ①在要使用log4j的类中,导入包org.apache.log4j.Logger

  ②日志对象,参数为当前类的class

1 static Logger logger = Logger.getLogger(UserTest.class);

  ③日志级别

1 logger.info("info:进入了testLog4j");
2 logger.debug("debug:进入了testLog4j");
3 logger.error("error:进入了testLog4j");

 

10、分页

  使用分页是为了减少数据的处理量。

(1)使用Limit分页

语法:SELECT * from user limit startIndex,pageSize;
SELECT * from user limit 3; #[0,n]

 

(2)使用Mybatis实现分页

  ①接口

1 List<User> getUserByLimit(Map<String,Integer> map);

 

  ②Mapper.xml

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

 

  ③测试

 1 @Test
 2 public void testSelectByLimit(){
 3     SqlSession sqlSession = MybatisUtils.getSqlSession();
 4     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 5     Map<String,Integer> map = new HashMap<String,Integer>();
 6     map.put("startIndex",0);
 7     map.put("pageSize",2);
 8     List<User> userList = mapper.getUserByLimit(map);
 9     for (User user : userList) {
10         System.out.println(user);
11     }
12     sqlSession.close();
13 }

 

(3)RwoBounds分页

  不使用SQL实现分页。

  ①接口

1 List<User> getUserByRowBounds();

 

  ②Mapper.xml

<!--分页2-->
<select id="getUserByRowBounds"  resultMap="UserMap">
    select * from user
</select>

 

  ③测试

 1 @Test
 2 public void testSelectByRwoBounds(){
 3     SqlSession sqlSession = MybatisUtils.getSqlSession();
 4     //RowBounds实现
 5     RowBounds rowBounds = new RowBounds(0,2);
 6     //通过Java代码层面实现分页
 7     List<User> userList = sqlSession.selectList("com.my.dao.UserMapper.getUserByRowBounds", null,rowBounds);
 8 
 9     for (User user : userList) {
10         System.out.println(user);
11     }
12     sqlSession.close();
13 }

 

(4)分页插件

MyBatis 分页插件 PageHelper

 

11、使用注解开发

(1)注解开发

  使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

  本质是反射机制,底层使用了动态代理。

  ①注解在接口上实现

1 @Select("select * from user")
2 List<User> getUserList();

 

  ②需要在核心配置文件中绑定接口

<!--绑定接口-->
<mappers>
    <mapper class="com.my.dao.UserMapper"/>
</mappers>

 

  ③测试

 1 @Test
 2 public void test(){
 3     SqlSession sqlSession = MybatisUtils.getSqlSession();
 4     //底层原理:使用了反射
 5     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
 6     List<User> userList = mapper.getUserList();
 7     for (User user : userList) {
 8         System.out.println(user);
 9     }
10     sqlSession.close();
11 }

 

(2)设置自动提交

1 public static SqlSession getSqlSession(){
2     return sqlSessionFactory.openSession(true);
3 }

 

(3)CRUD

 1 @Select("select * from user where id = #{id}")
 2 User getUserById(@Param("id") int id);
 3 
 4 @Insert("insert into user values(#{id},#{name},#{pwd})")
 5 int insertUser(User user);
 6 
 7 @Update("update user set name = #{name},pwd = #{pwd} where id = #{id}")
 8 int updateUser(User user);
 9 
10 @Delete("delete from user where id = #{id}")
11 int deleteUser(int id);

 

12、Mybatis执行流程剖析

 

13、Lombok

  Lombok其实就是帮助我们编写getter或者equals方法的一个“工具”。其实他的魅力并不在于帮助我们简单的编写对应的getter或者更多的方法,还有一点在于,当我们的字段发生改变时,Lombok也会对相应的getter方法进行改变。

(1)使用步骤

  ①在IDEA中安装Lombok插件

注意:

IDEA 2020最后一个版本已经内置了Lombok插件,SpringBoot 2.1.x之后的版本也在Starter中内置了Lombok依赖

  

  ②引入相应的maven包

<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>

 

  ③在实体类上加注解

1 @Data
2 @AllArgsConstructor
3 public class User {}

 

(2)常用注解说明

  • @NonNull:用在方法参数前,会自动对该参数进行非空校验,为空抛出NPE(NullPointerException)

  • @Cleanup:自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出前会清理资源,生成try-finally的代码关闭流

  • @Getter/@Setter:用在属性上,不用自己手写setter和getter方法,还可指定访问范围

  • @ToString:用在类上,可以自动复写toString方法

  • @EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法

  • @NoArgsConstructor,@RequiredArgsConstructor,@AllArgConstructor:用在类上,自动生成无参构造和使用所有参数的有参构造函数。

  • @Data:用在类上,相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstrutor这些注解,对POJO类十分有用。

  • @Value:用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法。

  • @SneakyThrows:自动抛受检异常,而无需显示的方法上使用throws语句。

  • @Synchronized:用在方法上,将方法声明为同步的,并自动加锁。

  • @Getter(lazy=true):可以替代经典的Double Check Lock样板代码

 

14、多对一处理

(1)环境搭建

  ①创建teacher表和student表

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');

 

  ②创建实体类

 1 @Data
 2 public class Teacher {
 3     private int id;
 4     private String name;
 5 }
 6 
 7 @Data
 8 public class Student {
 9     private int id;
10     private String name;
11     private Teacher teacher;
12 }

 

  ③新建Mapper接口

1 public interface StudentMapper {}
2 public interface TeacherMapper {}

 

  ④建立Mapper.xml文件

    在resources目录下建立StudentMapper.xml和TeacherMapper.xml。

 

  ⑤在核心配置文件中绑定注册我们的Mapper接口或者文件

1 <mappers>
2     <mapper resource="StudentMapper.xml"/>
3     <mapper resource="TeacherMapper.xml"/>
4 </mappers>

 

(2)多对一处理

  ①按照查询嵌套处理

<!--查询学生以及该学生对应的老师的信息,子查询
        1.查询学生的信息
        2.根据tid查询老师的信息
    -->
<resultMap id="StudentTeacher" type="Student">
    <!--复杂的属性需要单独处理
            association:对象
            collection:集合
        -->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>

<select id="getStudentAndTeacher" resultMap="StudentTeacher">
    select * from student
</select>

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

 

  ②按照结果嵌套处理

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

<resultMap id="StudentTeacher2" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
    </association>
</resultMap>

 

(3)一对多处理

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

  ①实体类

 1 @Data
 2 public class Student {
 3     private int id;
 4     private String name;
 5     private int tid;
 6 }
 7 
 8 @Data
 9 public class Teacher {
10     private int id;
11     private String name;
12     //一个老师拥有多个学生
13     private List<Student> studentList;
14 }

 

  ②按照结果嵌套处理

<!--按结果嵌套-->
<select id="getTeacherAndStudents" resultMap="TeacherStudent">
    select s.id sid,s.name sname,t.id tid,t.name tname
    from student s,teacher t
    where s.tid = t.id and t.id = #{id}
</select>

<resultMap id="TeacherStudent" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--javaType:指定属性的类型    ofType:指定集合中的泛型信息-->
    <collection property="studentList" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

 

  ③按查询嵌套处理

<!--按查询嵌套-->
<select id="getTeacherAndStudents2" resultMap="TeacherStudent2">
    select * from teacher where id = #{id}
</select>

<resultMap id="TeacherStudent2" type="Teacher">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <collection column="id" property="studentList" javaType="ArrayList" ofType="Student" select="getStudents"/>
</resultMap>

<select id="getStudents" resultType="Student">
    select * from student where tid = #{id}
</select>

 

(4)小结

  ①关联—association [多对一 ]

  ②集合—collection [一对多]

  ③javaType & ofType

	1. JavaType用来指定实体类中属性的类型
2. ofType用来指定映射到List或者集合中的pojo类型,泛型中的约束类型

注意:

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

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

  • 如果问题不好排查错误,可以使用日志,建议使用Log4j

 

15、动态SQL

  • 动态SQL就是根据不同的条件生成不同的SQL。

  • 动态SQL本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码。

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

    建议:

    先在Mysql中写出完整的SQL,再对应的去修改成为我们的动态SQL实现通用即可

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

 

(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

 

  ②创建实体类

1 @Data
2 public class Blog {
3     private int id;
4     private String title;
5     private String author;
6     private Date createTime;
7     private int views;
8 }

 

  ③创建接口和Mapper.xml

 

(2)if标签

  这条语句提供了可选的查找文本功能。

  ①接口

1 //根据title查询blog
2 List<Blog> getBlogByTitleIf();
3 List<Blog> getBlogByTitleIf(String title);

 

  ②Mapper.xml

<select id="getBlogByTitleIf" parameterType="String" resultType="Blog">
    select * from blog where 1=1
    <if test="title != null">
        and title = #{title}
    </if>
</select>

 

(2)where

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

  ①接口

1 List<Blog> getBlogWhere(Map map);

 

  ②Mapper.xml

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

 

(3)choose、when、otherwise

  有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

  ①接口

1 List<Blog> getBlogChoose(Map map);

 

  ②Mapper.xml

<select id="getBlogChoose" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                and author = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

 

(4)set

  set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

  ①接口

1 int updateBlogSet(Map map);

 

  ②Mapper.xml

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

 

(5)trim

  • prefix—在前面添加代码

  • prefixOverrides—删除不合适的前面代码

  • suffix—在后面添加代码

  • suffixOverrides—删除不合适的后面代码

  如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

1 List<Blog> getBlogTrim(Map map);
<select id="getBlogTrim" parameterType="map" resultType="Blog">
    select * from blog
    <trim prefix="where" prefixOverrides="and |or">
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
        <if test="views != null">
            or views = #{views}
        </if>
    </trim>
</select>

 

  与 set 元素等价的自定义 trim 元素:

1 int updateBlogTrim(Map map);
<update id="updateBlogTrim" 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>

 

(6)foreach

  foreach 元素的功能非常强大,它允许你指定一个集合(collection),声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头(open)与结尾(close)的字符串以及集合项迭代之间的分隔符(separator)。

  ①接口

1 List<Blog> getBlogForeach(Map map);

 

  ②Mapper.xml

<!--select * from blog where id in (1,2,3)-->
<select id="getBlogForeach" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <foreach collection="ids" item="id"
                 open="id in(" separator="," close=")">
            #{id}
        </foreach>
    </where>
</select>

 

  ③测试

 1 @Test
 2 public void testForeach(){
 3     SqlSession sqlSession = MybatisUtils.getSqlSession();
 4     BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
 5     Map<String,Object> map = new HashMap<String,Object>();
 6     List<String> ids = new ArrayList<String>();
 7     ids.add("1");
 8     ids.add("2");
 9     ids.add("3");
10     map.put("ids",ids);
11     List<Blog> blogList = mapper.getBlogForeach(map);
12     for (Blog blog : blogList) {
13         System.out.println(blog);
14     }
15     sqlSession.close();
16 }

 

(7)SQL片段

  有时候,我们可能会将一些功能的部分抽取出来,从而方便复用。

  ①使用SQL标签抽取公共的部分

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

 

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

<select id="getBlogWhere" parameterType="map" resultType="Blog">
    select * from blog
    <where>
        <include refid="if-title-author"></include>
    </where>
</select>

 

注意:

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

  • 不要存在where标签

 

16、缓存

(1)简介

  ①什么是缓存[ Cache ]?

      • 存在内存中的临时数据

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

  ②为什么使用缓存?

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

  ③什么样的数据能使用缓存?

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

 

(2)Mybatis缓存

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

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

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

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

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

 

(3)一级缓存

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

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

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

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

测试步骤:

  ①开启日志

  ②测试在一个Session中查询两次相同的记录

  ③查看日志输出

缓存失效的情况:

  • 查询不同的东西

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

  • 查询不同的Mapper.xml

  • 手动清理缓存

1 sqlSession.clearCache();//手动清理缓存

 

(4)二级缓存

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

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

  • 工作机制

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

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

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

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

步骤:

  ①开启全局缓存

<settings>
    <!--显示开启二级缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

 

  ②在要使用二级缓存的Mapper中开启

<!--在当前的Mapper.xml中使用二级缓存-->
<cache/>

<!--也可以自定义一些参数-->
<!--创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的-->
<cache
  eviction="FIFO" 
  flushInterval="60000"
  size="512"
  readOnly="true"/>

 

  ③测试

小结:

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

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

  • 只有当会话提交,或者关闭的时候,才会提交到二级缓冲中

注意:

需要对实体类进行序列化,也就是继承Serializable接口,否则会报以下错误:

org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: com.my.pojo.User

 

(5)缓存原理

 

(6)自定义缓存—Ehcache

  Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,具有快速、精干等特点。

  ①导包

<!--Ehcache-->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

 

  ②在mapper中指定使用Ehcache缓存实现

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

 

  ③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="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>

 

 

课程的源码传送门如下,包括课程后面的29道实战题。

sumAll/Mybatis课程(狂神说)源码 - 码云 - 开源中国 (gitee.com)

 

 

 

posted @ 2021-08-26 11:44  sumAll  阅读(176)  评论(0编辑  收藏  举报