超详细Mybatis学习笔记(可供下载)

1、简介

image-20210220161359619

什么是MyBatis?

  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

持久层的理解

所谓的持久层就是把数据可以永久的保存存储到设备中,不像放到内存中那样断电就消失,

为什么需要Mybatis?

  • 帮助程序员将数据存入到数据库中
  • 为了方便
  • 传动的JDBC代码过于复杂,比如数据的建立连接,取出的封装…Mybatis使得简化,自动化

特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql

2、第一个Mybatis程序

搭建环境

创建一个mybatis数据库,并建立user表,添加三条信息

image-20210220163353395

建立一个普通的Maven项目,并删除src目录,作为父工程

导入Maven依赖

<!--    导入依赖-->
<dependencies>
    <!--Mybatis依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
   <!--mysql连接驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.38</version>
    </dependency>
    <!--Junit测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
</dependencies>

创建一个子模块,这样子模块依赖可以继承父工程

image-20210220163511287

编写Mybatis核心配置文件

在resoureces目录下建立一个mybatis-config.xml配置文件

从Mybatis官方文档中复制下来,修改diriver,url,username,password

<?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&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    
    <!-- 每一个Mapper.xml都需要在Mybatis核心配置文件中注册!   -->
    <mappers>
        <mapper resource="com/hs_vae/dao/UserMapper.xml"/>
    </mappers>
</configuration>

编写Mybatis工具类

/**
 * Mybatis工具类,通过SqlSessionFactory工厂生产SqlSession
 */
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();
        }
    }

    /**
     *  有了sqlSessionFactory对象就可以从中获得SqlSession实例
     *  SqlSession里包含了数据库执行sql命令所需的所有方法
     */
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

编写代码

建立User实体类

对应的是user表

package com.hs_vae.pojo;

/**
 * User实体类
 */
public class User {
    private int id;
    private String name;
    private String pwd;

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

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getPwd() {
        return pwd;
    }

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

    public User() {
    }

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

创建UserMapper接口

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

创建UserMapper接口实现的xml文件

接口实现类由原来的UserDaoImpl转变成为一个Mapper配置文件

新建一个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绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.hs_vae.dao.UserMapper">
<!--id为执行的方法,resultType为返回的类型-->
    <select id="getUserList" resultType="com.hs_vae.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

由于Maven约定大于配置,放在java目录下的UserMapper.xml文件不能被导出,要在pom.xml文件里面配置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>

测试

public class UserDaoTest {
    @Test
    public void getUsers(){
        //第一步,获得SQLSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //第二步,执行sql
        UserDao userDao = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userDao.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }
        //第三步,关闭SqlSession
        sqlSession.close();
    }
}

测试后,报错,解决方案:mybatis-config.xml里修改url将useSSL=false,我是没有该mysql配置文件ssl用不了

image-20210220183248548

修改后,再测试,查询成功

image-20210220193429279

3、CRUD(增删改查)

3.1、几个属性

  • namespace:绑定一个对应的Dao/Mapper接口,包名要和Dao/Mapper接口的包名保持一致
  • id:就是对应的namespace中的方法名,相当于重写方法
  • resultType:sql语句执行的返回值
  • parameterType:参数类型

3.2、select

案例:查询指定Id的User信息

UserMapper编写一个通过id查询的抽象方法

public interface UserMapper {
    List<User> getUserList();
    //通过id查询user
    User getUserById(int id);
}

UserMapper.xml添加select标签

    <!--
        select查询指定id的user语句,
        id对应方法名字,resultType对应返回类型
        parameterType代表参数类型
    -->
    <select id="getUserById" parameterType="int" resultType="com.hs_vae.pojo.User">
        select * from  mybatis.user where id = #{id}
    </select>

测试类,添加一个Junit单元测试

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

    //第二步,执行sql
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User userById = mapper.getUserById(2);
    System.out.println(userById);
    //第三步,关闭SqlSession
    sqlSession.close();
}

运行结果,查询到了id为2的user

image-20210220200042879

3.3、insert

UserMapper接口添加insertUser()抽象方法

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

    //查询一个用户
    User getUserById(int id);

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

UserMapper.xml添加insert标签

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

测试类中,添加一个Junit单元测试

注意:增删改语句都要提交事务:sqlSession.commit()

@Test
public void insertUser(){
    //第一步,获得SQLSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //第二步,执行sql
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.insertUser(new User(4,"vae","666454"));
    //第四步,提交事务
    sqlSession.commit();
    System.out.println("插入成功");
    //第五步,关闭sqlSession
    sqlSession.close();
}

启动测试,插入成功

image-20210220210601767

3.4、delete

UserMapper接口添加deleteUser()抽象方法

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

    //查询一个用户
    User getUserById(int id);

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

    //删除一个用户
    int deleteUser(int id);
}

UserMapper.xml添加delete标签

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

测试类中,添加一个Junit单元测试,删除id=4的用户

@Test
public void deleteUser(){
    //第一步,获得SQLSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //第二步,执行sql
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int i = mapper.deleteUser(4);
    if (i>0){
        System.out.println("删除该用户成功");
        //第四步,提交事务
        sqlSession.commit();
    }else {
        System.out.println("删除失败");
    }
    //第五步,关闭sqlSession
    sqlSession.close();
}

run该方法,删除成功!

image-20210220211809286

3.5、update

UserMapper接口添加updateUser()抽象方法

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

    //查询一个用户
    User getUserById(int id);

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

    //删除一个用户
    int deleteUser(int id);

    //修改一个用户
    int updateUser(User user);
}

UserMapper.xml添加update标签

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

测试类中,添加一个Junit单元测试,修改id=3的用户信息

@Test
public void updateUser(){
    //第一步,获得SQLSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //第二步,执行sql
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int result = mapper.updateUser(new User(3, "薛之谦", "454455"));
    if (result>0){
        System.out.println("修改该用户成功");
        //第四步,提交事务
        sqlSession.commit();
    }else {
        System.out.println("修改失败");
    }
    //第五步,关闭sqlSession
    sqlSession.close();
}

run该方法,修改该用户信息成功!

image-20210220212505050

3.6、Map的巧用

当我们的实体类,或者数据库中的表,字段或者参数过多,我们可以考虑使用Map

下面举一个例子:

当我们去修改密码时候,我们只要知道用户的id就可以了,没有必要知道他的名字,像上面那样使用实体类User当参数类型的话,要将id,name,pwd全部写出来,而且名字必须和实体类保持统一,很麻烦

而我们使用Map当参数类型时,只需要传递其中的key,而且key的名字可以随意取,不用在为了修改密码写上用户的名字,等字段多了,很是头疼

先在UserMapper接口添加以Map为类型的抽象方法

//修改一个用户
int updateUser2(Map<String,Object> map);

在UserMapper.xml里添加update标签

对比于上面的update标签,传递的是Map里的key,发现这里的key可以随意命名,并且不需要在写多余的字段(name)了

<update id="updateUser2" parameterType="map">
    update mybatis.user set pwd=#{userPwd} where id=#{userId};
</update>

测试类中,添加一个Junit单元测试方法

@Test
//使用Map当参数类型
public void updateTest2(){
    //第一步,获得SQLSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //第二步,执行sql
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String,Object> map = new HashMap<String, Object>();
    //将需要修改的用户id和修改后的密码存入map中
    map.put("userPwd","456789");
    map.put("userId",1);
    int i = mapper.updateUser2(map);
    if (i>0){
        System.out.println("修改该用户成功");
        //第四步,提交事务
        sqlSession.commit();
    }else {
        System.out.println("修改失败");
    }
    //第五步,关闭sqlSession
    sqlSession.close();
}

run该方法,测试结果,id=1的用户密码成功被修改,name不受影响

image-20210220220425424

总结:

Map传递参数,直接在sql中取出key即可 paramType=“map”

对象传递参数,直接在sql中取对象的属性即可,paramType=“Object”

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

多个参数用Map,或者注解

3.7、模糊查询

案例:给出user表,里面有两个姓"胡"的用户,要求能够查出姓为"胡"的全部用户信息

image-20210220222151837

有两种方法

方法一:直接在sql拼接中使用通配符

<select id="getUserList" resultType="com.hs_vae.pojo.User" >
     select * from mybatis.user where name like "%"#{value}"%";
</select>

方法二:在java代码里使用 % %通配符

List<User> userList = userMapper.getUserList("%胡%");

查询结果

image-20210220222936805

4、配置解析(存在优化)

4.1、核心配置文件

  • mybatis-config.xml
  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
  • 配置文档的顶层结构如下:
    • configuration(配置)
      • properties(属性)
      • settings(设置)
      • typeAliases(类型别名)
      • typeHandlers(类型处理器)
      • objectFactory(对象工厂)
      • plugins(插件)
      • environments(环境配置)
        • environment(环境变量)
          • transactionManager(事务管理器)
          • dataSource(数据源)
      • databaseIdProvider(数据库厂商标识)
      • mappers(映射器)

所有的标签必须按照以上顺序写properties最顶端,mapper最底部

4.2、环境配置(environments)

Mybatis可以配置成适应多种环境

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

Mybatis默认的事务管理器就是JDBC,一共有两个: JDBC和MANAGED

默认的数据源:POOLED

4.3、属性 (properties)

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

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

在resource目录下建立mysql.properties

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中引入,(要放在顶部)

<!--引入外部配置文件-->
<properties resource="mysql.properties">
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</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>

总结:

  • 可以直接引入外部文件
  • 也可以在其中增加一些外部文件里没有的属性配置
  • 如果两个文件有同一个字段,优先使用外部配置文件的

4.4、类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写

例如:将com.hs_vae.pojo包下的User实体类,设置别名为user

<!--设置别名-->
<typeAliases>
    <typeAlias type="com.hs_vae.pojo.User" alias="user"/>
</typeAliases>

设置后,可以在UserMapper.xml的resultType中使用别名user

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

也可以扫描指定的包,实体类类名按照首字母小写设置别名,但是必须保证两者小写或大写是一样的

<!--设置别名-->
<typeAliases>
    <!--   扫描实体类的包     -->
    <package name="com.hs_vae.pojo"/>
</typeAliases>

扫描后的包下的实体类可以使用@Alias注解设置自定义的别名

image-20210221001807201

4.5、设置(settings)

下面列举一些重要的设置

日志实现设置

image-20210221002117694

缓存机制

image-20210221002216178

4.6、映射器 (mappers)

MapperRegistry:注册绑定我们的Mapper文件

有以下几种方式注册绑定

方式一:使用resource资源引用注册绑定 (推荐)

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

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

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

注意:接口和它的Mapper.xml配置文件必须同名且必须在同一个包下

方式二:使用扫描包进行注入绑定注册

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

注意:接口和它的Mapper.xml配置文件必须同名且必须在同一个包下

4.7、生命周期和作用域(scope)

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

image-20210221193720823

SqlSessionFactoryBuilder

  • 一旦创建了SqlSessionFactory,就不再需要它了
  • 局部变量

SqlSessionFactory

  • 类似于数据库连接池

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

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

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

SqlSession

  • 连接到连接池的一个请求
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 用完之后需要赶紧关闭,否则资源被占用

image-20210221194742683

这里的每一个Mapper代表一个具体的业务

5、属性名和字段名不一致问题

问题

数据库的User表的字段

image-20210221200735268

User实体类的属性

private int id;
private String name;
private String password;

两个密码是不一样的,一个是pwd,一个是password,这种情况下会出现查询密码为空的问题

image-20210221201902950

解决方案:可以使用resultMap结果集映射

resultMap

结果集映射

id name  pwd
id name  password
<!--结果集映射-->
<resultMap id="UserMap" type="User">
    <!--column代表数据库中的字段,property代表实体类的属性-->
    <result property="password" column="pwd"/>
</resultMap>

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

测试,查询到了密码

image-20210221203238607

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

6、日志

6.1、日志工厂

如果一个数据库操作,出现了异常,我们需要去排错,就需要用到日志

image-20210221204852896

有以下一些日志

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

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

STDOUT_LOGGING标准日志输出

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

注意这里的name和value都必须和官方文档保持统一,注意不要有空格

使用日志后测试,出现了日志信息

image-20210221205352136

6.2、LOG4J

什么是LOG4J?

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
  • 我们也可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
  • 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

导入依赖

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

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/hs.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{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

配置log4j为日志的实现

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

log4j的使用,直接测试

image-20210221212124254

生成的日志文件发现打不开,文件提示问号

image-20210221214002365

原因是在mybatis-config.xml配置文件中,有Package扫描包,需要将它删掉

<!--设置别名-->
<typeAliases>
    <typeAlias type="com.hs_vae.pojo.User" alias="user"/>
    <package name="com.hs_vae.pojo"/>
</typeAliases>

删掉后测试,可以成功打开

image-20210221214207864

简单使用

在要使用log4j的类中,导入的是org.apache.log4j.Logger;

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

static Logger logger = Logger.getLogger(UserMapper.class);

日志级别

@Test
public void testLog4j(){
    logger.info("info:已经进入了testLog4j");    
    logger.debug("debug:已经进入了testLog4j");
    logger.error("error:已经进入了testLog4j");
}

测试后,日志信息都追加到了日志文件里面

image-20210221214748213

7、分页

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

7.1、使用Limit分页

在UserMapper接口创建分页的抽象方法(使用Map传递参数)

public interface UserMapper {
    //查询一个用户
    User getUserById(int id);

    //分页查询
    List<User> getUserLimit(Map<String,Integer> user);
}

UserMapper.xml编写select标签

<!--结果集映射-->
<resultMap id="UserMap" type="User">
    <!--column代表数据库中的字段,property代表实体类的属性-->
    <result property="password" column="pwd"/>
</resultMap>

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

测试类里,添加一个Junit单元测试

@Test
public void testLimit(){
    //获取SqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //执行sql
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String,Integer> map = new HashMap<String, Integer>();
    map.put("startIndex",1);
    map.put("pageSize",2);
    List<User> userLimit = mapper.getUserLimit(map);
    for (User user : userLimit) {
        System.out.println(user);
    }
    sqlSession.close();
}

测试,查询第0页的两条信息

image-20210223194728487

8、使用注解开发

查询注解

@Select("select * from mybatis.user")
List<User> getUser();

不过简单的查询可以使用注解,如果复杂的语句就使用xml配置比较合适

CRUD

在工具类中将自动提交事务设置为true

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

UserMapper接口

//使用注解查询一个用户
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id1);

//使用注解增加一条数据
@Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
int addUser(User user);

//使用注解修改一条数据
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);

//使用注解删除一条数据
@Delete("delete from user where id=#{uid}")
int deleteUser(@Param("uid")int id);

测试

@Test
public void selectTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.getUserById(4);
    sqlSession.close();
}
@Test
public void addUserTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.addUser(new User(5,"yu","123132"));
    sqlSession.close();
}
@Test
public void updateTest(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    mapper.updateUser(new User(5, "ok", "1231123"));
    sqlSession.close();
}

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

使用注解进行简单的操作减少了很多JDBC的代码,变得很简单,不过这仅限于比较简单的SQL语句

关于@Param{}注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议都加上
  • 我们在Sql引用的就是我们这里的@Param{“uid”}中设定的属性

#{}和${}区别

  • #{}是预编译处理,$ {}是字符串替换。
  • MyBatis在处理#{}时,会将SQL中的#{}替换为?号,使用PreparedStatement的set方法来赋值;MyBatis在处理 $ { } 时,就是把 ${ } 替换成变量的值。
  • 使用 #{} 可以有效的防止SQL注入,提高系统安全性。

9、LomBok的使用

官方文档的一句话

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

简单的来说,就是使用注解让你的Java代码变得更简单,比如实体类中get和set等方法以及有参无参构造方法通过注解给你生成

在IDEA中安装lombok插件,完整完成后重启IDEA

image-20210224142404599

有以下注解可供使用

@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

安装后添加lombok的Maven依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
</dependency>

使用LomBok

没使用LomBok之前,一般都是这样编写实体类

/**
 * User实体类
 */
public class User {
    private int id;
    private String name;
    private String password;

    public User() {
    }

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

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

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

使用LomBok后,LomBok让代码变得非常简洁

/**
 * User实体类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String password;
}

image-20210224144209347

@Data 自动给你编写get、Set、equals等方法

@AllArgsConstructor 有参的构造方法

@NoArgsConstructor 无参的构造方法

LomBok有一定的弊端

  • JDK从Java 8升级到Java 11时,Lombok不能正常工作
  • 可读性差,而且不安全
  • 代码耦合度增加,当你使用Lombok来编写某一个模块的代码后,其余依赖此模块的其他代码都需要引入Lombok依赖,同时还需要在IDE中安装Lombok的插件

10、多对一处理

比如好几个学生对一个老师

建立一个student表和一个teacher表,并且将teacher的id设置为student的外键,使其关联

image-20210224161636336

案例需求:查询所有的学生信息,以及对应的老师信息

编写StudentMapper接口

public interface StudentMapper {
    List<Student> getStudent2();
}

将Student和Teacher设置别名

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

编写Mapper

<!-- 按照结果嵌套处理    -->
<select id="getStudent2" resultMap="StudentTeacher">
    select s.id sid,s.name sname,t.name tname
    from student s,teacher t
    where s.tid=t.id;
</select>

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

测试,编写一个Junit单元测试类

@Test
public void test02(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> student2 = mapper.getStudent2();
    for (Student student : student2) {
        System.out.println(student);
    }
    sqlSession.close();
}

image-20210225000843762

11、一对多处理

一个老师手上有多个学生

案例需求:查询一个指定的老师以及对应的所有学生

Teacher实体类和Student实体类

@Data
public class Teacher {
    private int tid;
    private String name;
    private List<Student> students;
}

@Data
public class Student {
    private int id;
    private String name;
}

TeacherMapper接口

public interface TeacherMapper {
    //查询一个指定的老师和对应的所有学生
    Teacher getTeacher(@Param("tid") int id);
}

编写sql

<select id="getTeacher" resultMap="StudentTeacher">
   select  t.id tid,t.name tname,s.id sid,s.name sname
   from student s,teacher t
   where t.id=s.tid;
</select>
<resultMap id="StudentTeacher" type="Teacher">
    <result property="tid" column="tid"/>
    <result property="name" column="tname"/>
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
    </collection>
</resultMap>

Junit单元测试

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

测试结果

image-20210225130951849

总结

  1. 关联使用association (多对一)
  2. 集合使用collection (一对多)
  3. javaType和ofType
    • javaType 用来指定实体类中属性的类型
    • ofType用来指定映射到List或者集合的pojo类型,即泛型约束类型

12、动态SQL

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

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

包含了以下标签

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

12.1、准备工作

创建一个博客表blog

image-20210225140112794

博客实体类

/**
 * 博客实体类
 */
@Data
public class Blog {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
}

这里的创建时间属性和blog表字段不对应

可以在核心配置文件中设置,将create_time映射成createTime

<!--是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
<setting name="mapUnderscoreToCamelCase" value="true"/>

创建一个随机生成博客id的工具类

public class UUIDUtils {
    public static String getUUID(){
        return UUID.randomUUID().toString().replaceAll("-","");
    }
}

12.2、IF

BlogMapper接口

public interface BlogMapper {
    //动态SQL查询(IF)
    List<Blog> getBlogIF(Map<String,Object> map);

    //插入数据
    int addBlog(Blog blog);
}

编写BlogMapp.xml动态SQL

当title为空时候或作者为空时候查询全部,title不为空,查询该title信息,可以动态查询

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

<insert id="addBlog" parameterType="blog">
    insert into blog(id,title,author,create_time,views) values(#{id},#{title},#{author},#{createTime},#{views});
</insert>

测试,编写一个Junit测试并插入数据

@Test
public void testIF(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,Object> map= new HashMap<String, Object>();
    map.put("title","Mybatis学习");
    List<Blog> blogIF = mapper.getBlogIF(map);
    for (Blog blog : blogIF) {
        System.out.println(blog);
    }
    sqlSession.close();
}

@Test
public void testAddBlog(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Blog blog = new Blog();
    blog.setId(UUIDUtils.getUUID());

    blog.setAuthor("胡帅");
    blog.setTitle("Mybatis学习");
    blog.setCreateTime(new Date());
    blog.setViews(123);
    mapper.addBlog(blog);

    blog.setTitle("Spring学习");
    blog.setCreateTime(new Date());
    blog.setViews(1000);
    mapper.addBlog(blog);

    blog.setTitle("SpringMVC学习");
    blog.setCreateTime(new Date());
    blog.setViews(500);
    mapper.addBlog(blog);

    sqlSession.close();
}

插入后的blog表,有三条blog信息

image-20210225155335180

title不为空时候,查询title="Mybatis学习"的blog信息

image-20210225143818883

title为空时,查询全部blog信息

image-20210225143847943

12.3、choose(when、otherwise)

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。

针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

BlogMapper接口添加一个抽象方法

//动态SQL(choose,when,otherwise)
List<Blog> getBlogChoose(Map<String,Object> map);

BlogMapper.xml里添加select,当title

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

测试类

@Test
public void testChoose(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,Object> map = new HashMap<String, Object>();
    map.put("id","2e4c4a542cdf45fbbfccd8fb3407223d");
    map.put("title","Mybatis学习");
    map.put("views",1000);
    List<Blog> blogChoose = mapper.getBlogChoose(map);
    for (Blog blog : blogChoose) {
        System.out.println(blog);
    }
    sqlSession.close();
}

只传入id=“2e4c4a542cdf45fbbfccd8fb3407223d”,对应上面的blog表,那么就可以查询到三条信息

image-20210225155711987

传入id="2e4c4a542cdf45fbbfccd8fb3407223d"和title="Mybatis学习"后

image-20210225155757860

当传入title="Mybatis学习"和views=1000时,只查询到了符合title的信息,说明当when有一个条件符合时,就结束了,不会在执行下面的元素

image-20210225160242652

总结:可以利用choose元素从多个条件里面选择一个条件去执行,一旦有一个条件符合就会结束,类似于switch语句

12.4、where

前面是if和choose元素都要设置where后面的1=1,是为了避免出现错误

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

如果没有匹配到任何条件,那么SQL变成这样:

select  * from blog
where 

这个查询显然是错误的

如果匹配到了第二个条件,那么SQL变成这样:

select  * from blog
where 
and author=?

这个查询也是错误的

这个问题我们可以使用where属性来解决这问题

先在BlogMapper接口添加一个抽象方法

//动态SQL(where)
List<Blog> getBlogWhere(Map<String,Object> map);

在BlogMapper.xml里编写sql

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

测试

@Test
public void testWhere(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,Object> map = new HashMap<String, Object>();
    map.put("title","Mybatis学习");
    map.put("views",1000);
    List<Blog> blogChoose = mapper.getBlogWhere(map);
    for (Blog blog : blogChoose) {
        System.out.println(blog);
    }
    sqlSession.close();
}

传入的是title="Mybatis学习"和views=1000查看blog表,没有同时满足这两个条件这条信息

image-20210225161805468

单独传入views=1000,可以发现where属性自动将and给删除掉了,

image-20210225161921164

总结:

  • where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。

  • 若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

12.5、set

用于动态更新Update语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列

同理和上面的一样,添加接口抽象方法,就不在写了

BlogMapper.xml编写update

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

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号

测试

@Test
public void testSet(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,Object> map = new HashMap<String, Object>();
    map.put("title","Mybatis学习");
    map.put("views",1000);
    map.put("id","2e4c4a542cdf45fbbfccd8fb3407223d");
    mapper.updateBlogSet(map);
    sqlSession.close();
}

image-20210225163219199

更新了三条符合id的信息

12.6、SQL片段

有时候,我们可能将一些功能的部分抽取出来,方便复用,可以使用SQL标签

  1. 使用SQL标签抽取公共的部分
<!--SQL片段-->
<sql id="if-title-views">
    <if test="title!=null">
        title=#{title}
    </if>
    <if test="views!=null">
        and views=#{views}
    </if>
</sql>
  1. 在需要使用的地方使用include标签引用即可
<select id="getBlogWhere" resultType="Blog" parameterType="map">
    select * from blog
    <where>
        <include refid="if-title-views"></include>
    </where>
</select>

注意:

  • 最好基于单表来定义SQL片段
  • SQL片段不要存在where标签

13、缓存

13.1、简介

  1. 什么是缓存?

    • 缓存是存在内存中的临时数据

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

  2. 为什么要使用缓存?

    • 减少和数据库的交互次数,减少系统开销,提高系统效率
  3. 什么样的数据能使用缓存?

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

13.2、Mybatis缓存

  • MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制,极大的提升查询效率
  • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
    • 默认情况下,只有一级缓存开启,它仅仅对一个会话中的数据进行缓存(SqlSession级别,也称本地缓存)
    • 二级缓存需要手动开启和配置,它是基于namespace级别的缓存
    • 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现该接口来自定义二级缓存

13.3、一级缓存

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

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

测试:

user表

image-20210225185756496

开启日志

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

在一个SqlSession中查询两次相同记录

@Test
public void test01(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user1 = mapper.getUserById(1);
    System.out.println(user1);
    User user2 = mapper.getUserById(1);
    System.out.println(user2);
    //判断查询后两个user是否相等
    System.out.println(user1==user2);
    sqlSession.close();
}

查看日志输出

image-20210225190040401

发现查询两次id=1的user,发现sql只执行一次

缓存失效的情况

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

image-20210225191047394

  1. 查询不同的东西
  2. 查询不同的Mapper
  3. 手动清除 SqlSession.clearCache()

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

13.4、二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
    • 新的会话查询信息,就可以从二级缓存中获取内容
    • 不同的namespace查出的数据会放在自己对应的缓存中

开启全局缓存(核心配置文件中)

<!--开启全局缓存-->
<setting name="cacheEnabled" value="true"/>

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

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

也可以自定义一些参数

<!--在当前的UserMapper.xml中使用二级缓存-->
<cache
        eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="true"
/>

测试,Junit单元测试

@Test
public void test01(){
    //创建第一个SqlSession
    SqlSession sqlSession1 = MybatisUtils.getSqlSession();
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    User user1 = mapper1.getUserById(1);
    System.out.println(user1);
    //关掉SqlSession1,相当于结束会话
    sqlSession1.close();

    //获取第二个SqlSession
    SqlSession sqlSession2 = MybatisUtils.getSqlSession();
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    User user2 = mapper2.getUserById(1);
    System.out.println(user2);
    sqlSession2.close();

    //判断查询后两个user是否相等
    System.out.println(user1==user2);
}

测试结果,第一个SqlSession关掉后,相当于结束了一级缓存,但是里面查询的数据放到了二级缓存中,可以供第二个SqlSession使用

image-20210225194201680

小结:

  • 只要开启了二级缓存,在同一个Mapper下就有效,上面两个SqlSession都在同一个UserMapper下
  • 所有的数据都会放在一级缓存中
  • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中

13.5、自定义缓存-ehcache

EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。

导入Maven ehcache依赖

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.0.3</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 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"/>
</ehcache>

笔记下载

关注微信公众号"自定义的Vae",下载Mybatis学习笔记在这里插入图片描述


版权声明:本文为qq_46350148原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。