Mybatis基础入门

Mybatis

我的环境:

mysql:8.0.28

jdk:1.8

mybatis:3.5.9

junit

一、初识mybatis

1.1 概述

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

1.2 第一个mybatis程序

总的来说,用mybatis就可以不用写繁琐的JDBC代码。

来写一个mybatis程序就明白了

  1. 准备数据库:

    DROP DATABASE IF EXISTS `mybatis`;
    CREATE DATABASE `mybatis`;
    
    CREATE TABLE `user` (
      `id` int unsigned NOT NULL AUTO_INCREMENT,
      `name` varchar(20) NOT NULL,
      `pwd` varchar(20) NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8
    
    insert into `user`(`name`,`pwd`) values ('root','123456'),('admin','123456');
    
  2. 导入依赖

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    
  3. 编写实体类

    package vip.yangsf.pojo;
    
    public class User {
        private int id;
        private String name;
        private String pwd;
    
        public User() {
        }
    
        public User(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.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;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    }
    
  4. 编写持久层接口

    package vip.yangsf.dao;
    
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        List<User> getUserList();
    }
    
    
  5. 配置映射文件 UserMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- namespace 填接口位置 -->
    <mapper namespace="vip.yangsf.dao.UserMapper">
        <!-- id填方法名称 resultType是结果类型 -->
        <select id="getUserList" resultType="vip.yangsf.pojo.User">
            select * from mybatis.user
        </select>
    </mapper>
    
  6. 编写mybatis配置文件mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <environments default="development">   
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    <!-- 注册刚刚写的映射文件 -->
        <mappers>
            <mapper resource="vip/yangsf/dao/UserMapper.xml"/>
        </mappers>
    </configuration>
    
  7. 编写工具类

    package vip.yangsf.First;
    
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    public class FirstUtil {
        // 官网扒下来的代码稍加改造
        private static String resource =null;
        private static InputStream inputStream = null;
        private static SqlSessionFactory sqlSessionFactory = null;
        static {
            resource = "mybatis-config.xml";
            try {
                // 加载配置文件,创建工厂
                inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory  = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public static SqlSession getSqlSession() {
            // 利用工厂创建SqlSession,SqlSession可以执行sql语句
            return sqlSessionFactory.openSession();
        }
    }
    
  8. 测试

    package vip.yangsf.dao;
    
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    import vip.yangsf.First.FirstUtil;
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public class UserDaoTest {
        @Test
        public void test() {
            SqlSession session = FirstUtil.getSqlSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
            List<User> userList = mapper.getUserList();
            
            for (User user : userList) {
                System.out.println(user);
            }
    
            session.close();
        }
    }
    

    输出:

    在这里插入图片描述

1.3 第一个mybatis程序的坑

按部就班的来,一般情况下只会遇到一个问题:找不到映射文件。

我当时排错排了很久,以为是版本原因,排了很久过后发现我注册mapper时路径结尾多了一个空格。。。。。

当然还有一个maven的问题,因为maven的约定大于配置,所以我们写在dao包下的配置文件无法导出,maven只将resources目录下的资源导出。于是,我们得去配置pom.xml:

<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>

在pom添加这一段配置,就可以导出包中的资源文件。

二、mybatis的CRUD

不管我们怎么花里胡哨,mybatis始终时要用来CRUD的。

CRUD和第一个mybatis程序流程一样,我们只需要给UserMapper接口添加方法,然后在映射文件UserMapper,xml中添加映射。

2.1 增

  1. 增的方法:

    package vip.yangsf.dao;
    
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        // 查询全部User
        List<User> getUserList();
        // 添加User
        int addUser(User user);
    }
    
  2. 修改UserMapper.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- namespace 填接口名 -->
    <mapper namespace="vip.yangsf.dao.UserMapper">
        <!-- id填方法名称 resultType是结果类型 -->
        <select id="getUserList" resultType="vip.yangsf.pojo.User">
            select * from mybatis.user
        </select>
        <!-- id填方法名称 parameterType是参数类型 -->
        <insert id="addUser" parameterType="vip.yangsf.pojo.User">
            <!-- #{}这些是变量,名称要与实体类中的成员变量名相对应 -->
            insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
        </insert>
    </mapper>
    
  3. 测试:

    class UserDaoTest {
        @Test
        public void test() {
            SqlSession session = FirstUtil.getSqlSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
    		mapper.addUser(new User(3,"yaf","123456"));
            // 提交事务,不然添加无效
            session.commit();
            // 释放资源
            session.close();
        }
    }
    

2.2 删

  1. 和增一样的道理,先在UserMapper接口中添加删除方法:

    package vip.yangsf.dao;
    
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        // 查询全部User
        List<User> getUserList();
        // 添加User
        int addUser(User user);
        // 通过id删除User
        int deleteUser(int id);
    }
    
  2. 修改UserMapper.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- namespace 填接口位置 -->
    <mapper namespace="vip.yangsf.dao.UserMapper">
        <!-- id填方法名称 resultType是结果类型 -->
        <select id="getUserList" resultType="vip.yangsf.pojo.User">
            select * from mybatis.user
        </select>
        <!-- id填方法名称 parameterType是参数类型 -->
        <insert id="addUser" parameterType="vip.yangsf.pojo.User">
            <!-- #{}这些是变量,名称要与实体类中的成员变量名相对应 -->
            insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
        </insert>
        
        <delete id="deleteUser" parameterType="int">
            delete from mybatis.user where id = #{id};
        </delete>
    </mapper>
    
  3. 测试:

    class UserDaoTest {
        @Test
        public void test() {
            SqlSession session = FirstUtil.getSqlSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
            // 删除id为3的记录
            mapper.deleteUser(3);
            // 提交事务,不然添加无效
            session.commit();
            // 释放资源
            session.close();
        }
    }
    

2.3 改

  1. 一样的套路,先添加修改方法:

    package vip.yangsf.dao;
    
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        // 查询全部User
        List<User> getUserList();
        // 添加User
        int addUser(User user);
        // 通过id删除User
        int deleteUser(int id);
        // 修改User
        int updateUser(User user);
    }
    
  2. 修改xml配置,添加映射:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- namespace 填接口位置 -->
    <mapper namespace="vip.yangsf.dao.UserMapper">
        <!-- id填方法名称 resultType是结果类型 -->
        <select id="getUserList" resultType="vip.yangsf.pojo.User">
            select * from mybatis.user
        </select>
        <!-- id填方法名称 parameterType是参数类型 -->
        <insert id="addUser" parameterType="vip.yangsf.pojo.User">
            <!-- #{}这些是变量,名称要与实体类中的成员变量名相对应 -->
            insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
        </insert>
        
        <delete id="deleteUser" parameterType="int">
            delete from mybatis.user where id = #{id};
        </delete>
        
        <update id="updateUser" parameterType="vip.yangsf.pojo.User">
            update mybatis.user set name = #{name}, pwd = #{pwd} where id=#{id};
        </update>
    </mapper>
    
  3. 测试:

    class UserDaoTest {
        @Test
        public void test() {
            SqlSession session = FirstUtil.getSqlSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
    		mapper.updateUser(new User(2, "ysf", "123456"));
            // 提交事务,不然添加无效
            session.commit();
            // 释放资源
            session.close();
        }
    }
    

2.4 查

查和增删改不一样的是,查不需要提交事务,因为没有修改表。

其余都是套路:

  1. 添加查询方法

    package vip.yangsf.dao;
    
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        // 查询全部User
        List<User> getUserList();
        // 添加User
        int addUser(User user);
        // 通过id删除User
        int deleteUser(int id);
        // 修改User
        int updateUser(User user);
        // 通过id查询某条记录
        User getUserById(int id);
    }
    
  2. 添加映射

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- namespace 填接口位置 -->
    <mapper namespace="vip.yangsf.dao.UserMapper">
        <!-- id填方法名称 resultType是结果类型 -->
        <select id="getUserList" resultType="vip.yangsf.pojo.User">
            select * from mybatis.user
        </select>
        <!-- id填方法名称 parameterType是参数类型 -->
        <insert id="addUser" parameterType="vip.yangsf.pojo.User">
            <!-- #{}这些是变量,名称要与实体类中的成员变量名相对应 -->
            insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
        </insert>
        
        <delete id="deleteUser" parameterType="int">
            delete from mybatis.user where id = #{id};
        </delete>
        
        <update id="updateUser" parameterType="vip.yangsf.pojo.User">
            update mybatis.user set name = #{name}, pwd = #{pwd} where id=#{id};
        </update>
        
        <select id="getUserById" resultType="vip.yangsf.pojo.User">
            select *
            from mybatis.user where id = #{id};
        </select>
    </mapper>
    
  3. 测试

    class UserDaoTest {
        @Test
        public void test() {
            SqlSession session = FirstUtil.getSqlSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.getUserById(1);
            System.out.println(user);
        }
    }
    

2.6 传参方式:Map传参

假如说要插入一个User,我们刚刚是通过UserMapper.xml来映射实体类,比如:

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

我们就需要传个User进去,这些变量名还得与User中的成员变量名,数据库中的字段名一样。

我们可以传Map进去,看代码就明白了:

  1. UserMapper接口中添加方法:

    package vip.yangsf.dao;
    
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        // 查询全部User
        List<User> getUserList();
        // 添加User
        int addUser(User user);
        // 通过id删除User
        int deleteUser(int id);
        // 修改User
        int updateUser(User user);
        // 通过id查询某条记录
        User getUserById(int id);
        // Map传参
        int addUser1(Map<String, Object> map);
    }
    
  2. 配置文件中添加一个映射:

    <!-- 参数类型为Map -->
    <insert id="addUser1" parameterType="Map">
        <!-- 变量名不用再像之前那样与字段名对应 -->
        insert into mybatis.user (name, pwd) values (#{username}, #{password});
    </insert>
    
  3. 测试:

    class MapTest {
        @Test
        public void test() {
            SqlSession sqlSession = FirstUtil.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            Map<String, Object> map = new HashMap<>();
            // key名要和配置文件中对应
            map.put("username", "map");
            map.put("password", "123456");
            // 传map过去就行
            mapper.addUser1(map);
            // 提交事务
            sqlSession.commit();
            // 释放资源
            sqlSession.close();
        }
    }
    

2.7 两种方式模糊查询

本质上都一样,一种是通过java传参时手动输入通配符,一种是把通配符写死了。

先添加两条记录待会儿查找:

在这里插入图片描述

  1. 接口中添加方法:

    List<User> getUserList1(String value);
    
    List<User> getUserList2(String value);
    
  2. 添加映射语句:

    <select id="getUserList1" resultType="vip.yangsf.pojo.User">
        select * from mybatis.user where name like #{value};
    </select>
    
    <select id="getUserList2" resultType="vip.yangsf.pojo.User">
        select * from mybatis.user where name like "%"#{value}"%";
    </select>
    
  3. 测试

    // 查询所有name中带李的记录
    class LikeUserTest {
        @Test
        public void test() {
            SqlSession sqlSession = FirstUtil.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
            System.out.println("方式一:");
            List<User> list = mapper.getUserList1("%李%");
            list.forEach(System.out::println);
    
            System.out.println("方式二:");
            List<User> list1 = mapper.getUserList2("李");
            list1.forEach(System.out::println);
    
            sqlSession.close();
        }
    }
    

    输出:

    在这里插入图片描述

2.8 注意事项

增删改一定要记得提交事务,不然改了白改。

三、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.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="vip/yangsf/dao/UserMapper.xml"/>
    </mappers>
</configuration>

接下里我们稍微深入一点,学习这里面的一些标签。

这段xml文档是从官网扒下来的,长这样子:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <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="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

可以发现,里面有几个像是变量的东西,比如:driver、url这些。可以猜想,这些值是可以通过外界传进来的。

3.1 properties(属性)

和properties有关系的是这几行代码。

 <property name="driver" value="${driver}"/>
 <property name="url" value="${url}"/>
 <property name="username" value="${username}"/>
 <property name="password" value="${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>
    
    <properties>
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=GMT%2B8"/>
        <property name="username" value="root"/>
        <property name="pwd" 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="${pwd}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="vip/yangsf/dao/UserMapper.xml"/>
    </mappers>
</configuration>

还可以引入外部文件:

db.properties:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username=root
pwd=123456

mybatis-config.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <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="${pwd}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="vip/yangsf/dao/UserMapper.xml"/>
    </mappers>
</configuration>

3.2 environments(环境配置)

通过标签名environments结尾的s,就可以猜想:是不是可以有多个环境?

事实上也是肯定的。

我们可以在environments中添加多个environment,但是每个SqlSessionFactory只能对应一个environment,我们在选择environment时可以通过environments的default属性的值来选择对应的environment,或是通过创建SqlSessionFactory时填写build方法的参数来选择。

3.2.1 transactionManager

事务管理器

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

MANAGEND不太懂,一般只用JDBC,后面的学习了Spring就不需要配置事务管理器了。

3.2.2 dataSource

数据源,默认是POOLED,还有UNPOOLED UNPOOLED不能使用池化技术,POOLED就像是数据库连接池,可以为它设置一些属性如poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10,等等一些配置数据库连接池的一些属性。

3.3 typeAliases(类型别名)

可以为Java类型设置一个缩写名字,仅用于xml配置文件中,比如之前是这样写的:

<select id="getUserList" resultType="vip.yangsf.pojo.User">
    select * from mybatis.user;
</select>

resultType后面的User全类名有点冗余,我们在mybatis-config.xml中加上typeAliases,给

vip.yangsf.pojo.User取个别名:

<typeAliases>
    <typeAlias type="vip.yangsf.pojo.User" alias="User"/>
</typeAliases>

标签的顺序也有讲究:

<?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="vip.yangsf.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="${pwd}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="vip/yangsf/dao/UserMapper.xml"/>
    </mappers>
</configuration>

然后,在映射文件中就可以用user(不区分大小写)来代替vip.yangsf.pojo.User:

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

当实体类较多时,可以直接给定一个包路径,mybatis会自动扫描这个包下面的所有JavaBean并给他们取个别名,别名就是类名:

<typeAliases>
    <package name="vip.yangsf.pojo"/>
</typeAliases>

如果有特别要求,可以在类上面加上@Alias("")来取类名:

@Alias("user")
public class User {
	……
}

mybatis为一些常见的 Java 类型内建的类型别名。它们都是不区分大小写的。可以去https://mybatis.net.cn/configuration.html#typeAliases查看。

3.4 mappers(映射器)

我们配置mybatis是为了执行sql,让mybatis找到要映射的文件,有四种方式:

  1. 使用相对路径

    <mappers>
        <mapper resource="vip/yangsf/dao/UserMapper.xml"/>
    </mappers>
    
  2. 使用url

    <mappers>
        <mapper url="file:E:\Mybatis\lesson01\src\main\java\vip\yangsf\dao\UserMapper.xml"/>
    </mappers>
    
  3. 使用对应的接口名

    <mappers>
        <mapper class="vip.yangsf.dao.UserMapper"/>
    </mappers>
    
  4. 使用包名,会将包中所有mapper注册

    <mappers>
        <package name="vip.yangsf.dao"/>
    </mappers>
    

注意事项:

映射文件尽量与映射器接口名一样,不然后两种无法识别!

四、生命周期和作用域

错误的控制生命周期和作用域会导致并发问题。

SqlSessionFactoryBuider:

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的应用逻辑代码
}

五、resultMap

现在有这么一张表:

CREATE TABLE `user_test` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(20) NOT NULL,
    `password` varchar(20) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 

insert into `user`(`name`,`password`) values ('root','123456'),('admin','123456');

有这么一个JavaBean:

package vip.yangsf.pojo;

public class User1 {
        private int id;
        private String name;
        private String pwd;

        public User1() {
        }

        public User1(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.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;
        }

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

然后我们查询所有的User1:

在这里插入图片描述

pwd查不到,是因为数据库中字段为password,JavaBean中是pwd,我们可以用结果集映射来解决这种数据库字段与JavaBean属性名不一致问题。

修改映射器文件:

<?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="vip.yangsf.dao.UserTestMapper">
    <resultMap id="User1Map" type="User1">
        <result column="password" property="pwd"/>
    </resultMap>
    <select id="getUserList" resultMap="User1Map">
        select * from mybatis.user_test
    </select>
</mapper>

然后就能查出来了。

六、日志

要打印日志,就要在mybatis-config.xml中添加日志配置。

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

STDOUT_LOGGING 是标准日志输出,可以直接用。

logImpl还有很多种日志框架可以选择:

在这里插入图片描述

log4j

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

  1. 使用阿帕奇的log4j,首先需要把包导进来:

    在pom.xml中:

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    
  2. 让mybatis使用log4j

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    
  3. 配置log4j,在resources目录下添加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/xxx.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
    
  4. 我们还可以往日志里添加语句、设置日志格式等

    package vip.yangsf.dao;
    
    
    import org.apache.log4j.Logger;
    import org.junit.Test;
    
    public class Log4jTest {
        static Logger logger = Logger.getLogger(Log4jTest.class);
    
        @Test
        public void test() {
            logger.info("info: 666");
            logger.error("error: 666");
            logger.debug("debug: 666");
        }
    }
    

七、分页

如果数据很多,就需要分页处理

7.1 Limit分页

mybatis的流程我们已经熟悉了,大致就是三步

  1. 写接口

    package vip.yangsf.dao;
    
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    import java.util.Map;
    
    public interface LimitUser {
        List<User> getLimitUser(Map<String, Object> map);
    }
    
  2. 写sql

    <?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="vip.yangsf.dao.LimitUser">
        <select id="getLimitUser" resultType="user">
            select * from mybatis.user limit #{startIndex}, #{pageSize};
        </select>
    </mapper>
    
  3. 测试

    @Test
    public void test() {
        SqlSession sqlSession = FirstUtil.getSqlSession();
        LimitUser mapper = sqlSession.getMapper(LimitUser.class);
    
        Map<String, Object> map = new HashMap<>();
        map.put("startIndex", 3);
        map.put("pageSize", 2);
        List<User> user = mapper.getLimitUser(map);
        user.forEach(System.out::println);
    }
    

7.2 RowBounds分页

java提供了一个类来帮助我们分页,就不用写分页的sql语句了(但查询还是要写)

  1. 写接口

    package vip.yangsf.dao;
    
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    import java.util.Map;
    
    public interface LimitUser {
        List<User> getLimitUser(Map<String, Object> map);
        List<User> getLimitUserByRowBounds();
    }
    
  2. 写sql

    <?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="vip.yangsf.dao.LimitUser">
        <select id="getLimitUser" resultType="user">
            select * from mybatis.user limit #{startIndex}, #{pageSize};
        </select>
    
        <select id="getLimitUserByRowBounds" resultType="user">
            select * from mybatis.user
        </select>
    </mapper>
    
  3. 测试

    @Test
    public void rowBounds() {
        SqlSession sqlSession = FirstUtil.getSqlSession();
    
        List<User> users = sqlSession.selectList("vip.yangsf.dao.LimitUser.getLimitUserByRowBounds", null, new RowBounds(3, 2));
    
        users.forEach(System.out::println);
    }
    

    效果和上面是一样的。

7.3 插件分页

还可以用mybatis插件进行分页,比如PageHelper

八、使用注解开

8.1 注解使用方法以及示例

步骤都是一样的,sql不用再写在xml里面

  1. 写接口

    package vip.yangsf.mapper;
    
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        List<User> getUserList();
    
    }
    
  2. 写sql

    直接添加注解就完事:

    package vip.yangsf.mapper;
    
    import org.apache.ibatis.annotations.Select;
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        @Select("select * from mybatis.user")
        List<User> getUserList();
    }
    
  3. 测试

    package vip.yangsf;
    
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    import vip.yangsf.mapper.UserMapper;
    import vip.yangsf.pojo.User;
    import vip.yangsf.utils.MyUtils;
    
    import java.util.List;
    
    public class Test01 {
        @Test
        public void test() {
            SqlSession sqlSession = MyUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = mapper.getUserList();
            userList.forEach(System.out::println);
        }
    }
    

8.2 使用注解完成CRUD

都是三步走!

  1. 写接口

    package vip.yangsf.mapper;
    
    import org.apache.ibatis.annotations.*;
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        // 查询
        List<User> getUserList();
    	// 根据id查询
        User getUserById(int id);
    	// 增
        int addUser(User user);
    	// 增,利用id自增,只需要填写姓名和密码
        int addUser1(String name, String password);
    	// 改
        int updateUser(User user);
    	// 删
        int deleteUser(int id);
    }
    
  2. 写sql

    package vip.yangsf.mapper;
    
    import org.apache.ibatis.annotations.*;
    import vip.yangsf.pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        // 查询
        @Select("select * from mybatis.user")
        List<User> getUserList();
    	// 根据id查询
        @Select("select * from mybatis.user where id = #{id}")
        User getUserById(int id);
    	// 增
        @Insert("insert into mybatis.user(id, name, pwd) values(#{id}, #{name}, #{pwd})")
        int addUser(User user);
    	// 增,利用id自增,只需要填写姓名和密码
        @Insert("insert into mybatis.user(name, pwd) values(#{name}, #{password})")
        int addUser1(@Param("name") String name, @Param("password") String password);
    	// 改
        @Update("update mybatis.user set name = #{name}, pwd = #{pwd} where id = #{id}")
        int updateUser(User user);
    	// 删
        @Delete("delete from mybatis.user where id = #{id}")
        int deleteUser(int id);
    }
    
  3. 写测试

    package vip.yangsf;
    
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    import vip.yangsf.mapper.UserMapper;
    import vip.yangsf.pojo.User;
    import vip.yangsf.utils.MyUtils;
    
    import java.util.List;
    
    public class Test01 {
        @Test
        public void test() {
            SqlSession sqlSession = MyUtils.getSqlSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    		// 查询全部
            List<User> userList = mapper.getUserList();
            userList.forEach(System.out::println);
    		// 根据id查询
            User user = mapper.getUserById(3);
            System.out.println(user);
    		// 增
            mapper.addUser(new User(6, "ysf", "123456"));
    		// 增
            mapper.addUser1("ysf", "123456");
    		// 改
            mapper.updateUser(new User(6, "yangsf", "123456"));
    		// 删
            mapper.deleteUser(6);
        }
    }
    

九、Lombok

Lombok项目是一个Java库,它会自动插入编辑器和构建工具中,Lombok提供了一组有用的注释,用来消除Java类中的大量样板代码。仅五个字符(@Data)就可以替换数百行代码从而产生干净,简洁且易于维护的Java类。

说起来复杂,但其实做起来很简单,导完包之后,就可以用它的各种注解来简化我们的代码了。

  1. 导包

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
    
  2. 在实体类上加注解,例如@Data

    package vip.yangsf.pojo;
    
    import lombok.Data;
    
    @Data
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
  3. 看结构

    在这里插入图片描述

发现多了很多方法 get/set, toString…………方法都有了,但是有参构造不见了,可以添加@AllArgsConstructor,添加这个注解后无参构造不见了,我们再加上@NoArgsConstructor。非常完美。

当然,不只有@Data注解,还有很多注解

例如

@Setter :注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。

@Getter :使用方法同上,区别在于生成的是getter方法。

@ToString :注解在类,添加toString方法。

@EqualsAndHashCode: 注解在类,生成hashCode和equals方法。

@NoArgsConstructor: 注解在类,生成无参的构造方法。

@RequiredArgsConstructor: 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。

@AllArgsConstructor: 注解在类,生成包含类中所有字段的构造方法。

@Data: 注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。

@Slf4j: 注解在类,生成log变量,严格意义来说是常量。

----百度百科

十、复杂的查询

真tm打脑壳。

10.1 多对一

先来看看多对一的情况。

现在有两张表:

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

我们需要的是,查询所有学生以及对应的老师信息。

sql这样写:

select s.id `学生id`, s.name `学生姓名`, t.id `老师id`, t.name `老师姓名` from student s inner join teacher t on s.tid = t.id

在这里插入图片描述

那再mybatis中应该怎么写呢?有两种方法,先看第一种

查询嵌套

这就复杂起来了,这不是sql简单复制就可以实现的,还是按照步骤来。

  1. 先写实体类

    Student的实体类,tid代表的是老师的信息,所以是Teacher类型,待会儿去老师表里查出来:

    package vip.yangsf.pojo;
    
    import lombok.Data;
    
    @Data
    public class Student {
        private int id;
        private String name;
        
        private Teacher tid;
    }
    

    Teacher的实体类:

    package vip.yangsf.pojo;
    
    
    import lombok.Data;
    
    @Data
    public class Teacher {
        private int id;
        private String name;
    }
    
  2. 写接口:

    package vip.yangsf.mapper;
    
    import vip.yangsf.pojo.Student;
    
    import java.util.List;
    
    public interface StudentMapper {
        List<Student> getList();
    }
    
  3. 写sql,这里就比较复杂了,我们先看一下答案:

    <?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="vip.yangsf.mapper.StudentMapper">
        <select id="getList" resultMap="StudentX">
            select * from mybatis.student
        </select>
        
        <resultMap id="StudentX" type="vip.yangsf.pojo.Student">
            <association property="tid" column="tid" javaType="vip.yangsf.pojo.Teacher" select="getTeachers"/>
        </resultMap>
    
        <select id="getTeachers" resultType="teacher">
            select * from mybatis.teacher where id = #{tid}
        </select>
    </mapper>
    

    这里有几个难点:

    • 查询Student返回的tid是int类型,但我们需要的是Teacher类型

      解决:定义resultMap

    • 查Teacher

      解决:定义一个查询,在resultMap中,调用这个查询,将查到的teacher返回给tid。

  4. 测试

    package vip.yangsf;
    
    import org.apache.ibatis.session.SqlSession;
    import vip.yangsf.mapper.StudentMapper;
    import vip.yangsf.pojo.Student;
    import vip.yangsf.utils.MyUtils;
    
    import java.util.List;
    
    public class test02 {
        public static void main(String[] args) {
            SqlSession sqlSession = MyUtils.getSqlSession();
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    
            List<Student> list1 = studentMapper.getList();
            list1.forEach(System.out::println);
        }
    }
    

    结果:

    在这里插入图片描述

再来看第二种,第二种的sql语句就简单得多

结果嵌套

实体类和接口已经写好了,直接开始写sql:

<?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="vip.yangsf.mapper.StudentMapper">
    
    <select id="getList" resultMap="StudentTeacher">
        select s.*, t.id as ttid, t.name as tname from mybatis.student s, mybatis.teacher t where s.tid = t.id;
    </select>
    
    <resultMap id="StudentTeacher" type="vip.yangsf.pojo.Student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <association property="tid" javaType="teacher">
            <result property="id" column="ttid"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>
    
</mapper>

不用嵌套sql,我们直接将查出来的字段名和java类的成员变量做映射。

测试还是一样的代码:

package vip.yangsf;

import org.apache.ibatis.session.SqlSession;
import vip.yangsf.mapper.StudentMapper;
import vip.yangsf.pojo.Student;
import vip.yangsf.utils.MyUtils;

import java.util.List;

public class test02 {	
    public static void main(String[] args) {
        SqlSession sqlSession = MyUtils.getSqlSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

        List<Student> list1 = studentMapper.getList();
        list1.forEach(System.out::println);
    }
}

结果:

在这里插入图片描述

10.2 一对多

一个老师对应多个学生,同样的也有两种方法,步骤大同小异。

结果嵌套
  1. 实体类

    teacher:

    import lombok.Data;
    
    import java.util.List;
    
    @Data
    public class Teacher {
        private int id;
        private String name;
    
        // 一对多
        private List<Student> students;
    }
    

    student:

    import lombok.Data;
    
    @Data
    public class Student {
        private int id;
        private String name;
        private int tid;
    }
    
  2. 写接口

    TeacherMapper

    import vip.yangsf.pojo.Teacher;
    
    import java.util.List;
    
    public interface TeacherMapper {
        List<Teacher> getList();
    }
    
  3. 写sql

    TeacherMapper.xml:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="vip.yangsf.mapper.TeacherMapper">
        
        <select id="getList" resultMap="TeacherStudent">
            select t.id as ttid, t.name as tname, s.* from mybatis.teacher t, mybatis.student s where tid = t.id
        </select>
        
        <resultMap id="TeacherStudent" type="teacher">
            <result property="id" column="ttid"/>
            <result property="name" column="tname"/>
            
            <collection property="students" column="tid" ofType="student">
                <result property="id" column="id"/>
                <result property="name" column="name"/>
                <result property="tid" column="tid"/>
            </collection>
            
        </resultMap>
    
    </mapper>
    

    将查出来的字段和Java实体类中一一对应

    由于 一个老师对应一个学生集合,所以将查出来的所有Student装在集合中,集合中的每一个对象类型用ofType设定,集合的结果集映射用collection标签,

    将查出来的字段和Student类中一一对应,Student就装在了集合中。

    Teacher的成员变量也和查出来的字段一一对应。

  4. 测试

    import org.apache.ibatis.session.SqlSession;
    import vip.yangsf.mapper.TeacherMapper;
    import vip.yangsf.pojo.Teacher;
    import vip.yangsf.utils.MyUtils;
    
    import java.util.List;
    
    public class Test03 {
        public static void main(String[] args) {
            SqlSession sqlSession = MyUtils.getSqlSession();
            TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    
            List<Teacher> teachers = mapper.getList();
            teachers.forEach(System.out::println);
        }
    }
    

    结果:

    在这里插入图片描述

查询嵌套

接口和实体类不再写,看看sql怎么写:

<select id="getList" resultMap="Teachers">
    select * from mybatis.teacher
</select>

<resultMap id="Teachers" type="teacher">
    <result property="id" column="id"/>
    <collection property="students" column="id" ofType="student" select="getStudents"/>
</resultMap>

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

将老师查出来,然后把查出来的 老师的 id 用在查询对应的学生中,所以我们定义了一个结果集映射,做了个嵌套查询,因为id用于子查询了,所以要将id载映射给Teacher对象的id。

测试:

package vip.yangsf;

import org.apache.ibatis.session.SqlSession;
import vip.yangsf.mapper.TeacherMapper;
import vip.yangsf.pojo.Teacher;
import vip.yangsf.utils.MyUtils;

import java.util.List;

public class Test03 {
    public static void main(String[] args) {
        SqlSession sqlSession = MyUtils.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

        List<Teacher> teachers = mapper.getList();
        teachers.forEach(System.out::println);
    }
}

结果:

在这里插入图片描述

个人觉得结果嵌套的方式更好用,因为只需要关心查询的结果,将查询出来的数据与实体类做映射就可以了。

复杂的查询确实复杂,需要总结一下新学到的东西:

  1. association标签,多对一,做对象的结果映射
  2. collection标签,一对多,做集合的结果映射
  3. 对应Java中的类用javaType
  4. 对应集合中元素类型用ofType

十一、动态SQL

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

说人话就是,根据条件选择拼接的语句,也就是在sql层面加了一些逻辑判断。

先搭个环境:

表:

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;

工具类:

获取SqlSession的工具类不必再多说,我们在这里建一个获取id的工具类(因为这次id不是主键自增)

使用UUID:

import java.util.UUID;

public class IDUtils {
    public static String getID() {
        // jian
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}

表有了,再插入几个数据:

活学活用,写个接口添加数据:

package vip.yangsf.mapper;

import org.apache.ibatis.annotations.Insert;
import vip.yangsf.entity.Blog;

import java.util.List;

public interface BlogMapper {
	@Insert("insert into mybatis.blog(id, title, author, create_time, views) values (#{id}, #{title}, #{author}, #{createTime}, #{views})")
    int addBlog(Blog blog);
}
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import vip.yangsf.entity.Blog;
import vip.yangsf.mapper.BlogMapper;
import vip.yangsf.utils.IDUtils;
import vip.yangsf.utils.MyUtils;

import java.util.Date;

public class Test01 {
    @Test
    public void test01() {
        SqlSession sqlSession = MyUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        for (int i = 1; i <= 5; i++) {
            mapper.addBlog(new Blog(IDUtils.getID(), "第" + i + "篇", (int)(Math.random()*100) + "", new Date(), 100));
        }

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

环境搭好后,表长这样

在这里插入图片描述

11.1 if

假如现在我们需要实现:根据title查找blog,如果输入有author,那么就根据title和author查找。

如果用JDBC,就需要判断和拼接sql,头大。

使用mybatis,就轻松的多:

写接口:

import vip.yangsf.entity.Blog;

import java.util.List;
import java.util.Map;

public interface BlogMapper {
    List<Blog> searchBlogs(Map map);
}

传入一个map,根据参数来选择查询方式。

写sql:

<select id="searchBlogs" resultType="Blog">
    select * from mybatis.blog
    <if test="title != null">
        where title like "%"#{title}"%"
    </if>
    <if test="author != null">
        and author like "%"#{author}"%"
    </if>
</select>

如果传入的title不为空,那么就拼接这条sql语句,如果author不为空,那么拼接sql语句。

测试:

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import vip.yangsf.mapper.BlogMapper;
import vip.yangsf.utils.MyUtils;

import java.util.HashMap;
import java.util.Map;

public class Test01 {
    @Test
    public void test01() {
        SqlSession sqlSession = MyUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        Map<Object, Object> map = new HashMap<>();
        map.put("title", "3");
        map.put("author", "0");
        System.out.println(mapper.searchBlogs(map));
        
        sqlSession.close();
    }
}

传入一个map。

11.2 where

上面程序有一个问题:如果不传入title,只传入author,那么sql就会错误。

如和选择一个语句执行呢?

当然,可以这样

<select id="searchBlogs" resultType="Blog">
    select * from mybatis.blog where 1=1
    <if test="title != null">
        and title like "%"#{title}"%"
    </if>
    <if test="author != null">
        and author like "%"#{author}"%"
    </if>
</select>

可是,若是要拼接or语句就会出问题,这时候,就可以用到where标签

<select id="searchBlogs" resultType="blog">
    select * from mybatis.blog
    <where>
        <if test="title != null">
            and title like "%"#{title}"%"
        </if>
        <if test="author != null">
            and author like "%"#{author}"%"
        </if>
    </where>
</select>

测试:

import java.util.HashMap;
import java.util.Map;

public class Test01 {
    @Test
    public void test01() {
        SqlSession sqlSession = MyUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        Map<Object, Object> map = new HashMap<>();
        map.put("author", "0");
        
        System.out.println(mapper.searchBlogs(map));
        sqlSession.close();
    }
}

当然也是成功的。

执行过程:

where标签下的,会选择性拼接,拼接很灵性,如果第一个条件开头是and或者or,那么where就会将and或者or替换掉,后面满足条件就拼接就完事了。

11.3 choose、when、otherwise

用了where标签过后,sql已经很动态了。

现在还有一种情况:有时候只需要满足一个条件,如果一个条件都不满足,那么就得返回全部数据,这不合理。

于是,choose、when、otherwise来了。类似于switch-case

<select id="searchBlogs" resultType="blog">
    select * from mybatis.blog
    <where>
        <choose>
            <when test="title != null">
                title like "%"#{title}"%"
            </when>
            <when test="author != null">
                author like "%"#{author}"%"
            </when>
            <otherwise>
                title = 1
            </otherwise>
        </choose>
    </where>
</select>

只能满足一个条件,所以不用and。如果都不满足则条件为title=1。

11.4 set

动态sql的查找我们倒是有些了解了,那修改呢?

  1. 写接口方法:

    int updateBlogs(Map map);
    
  2. 写sql

    这里就有一些讲究了

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

    会根据条件选择拼接哪条语句,并且会删掉最后一个逗号。

  3. 测试:

    import org.apache.ibatis.session.SqlSession;
    import vip.yangsf.mapper.BlogMapper;
    import vip.yangsf.utils.MyUtils;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Test02 {
        public static void main(String[] args) {
            SqlSession sqlSession = MyUtils.getSqlSession();
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    
            Map map = new HashMap();
            map.put("title", "第8篇");
            map.put("author", "张5");
            map.put("id", "a928fd4d8b7a4f689d92e383a06e140e");
            
            mapper.updateBlogs(map);
            
            sqlSession.commit();
            sqlSession.close();
        }
    }
    

    当然效果也是杠杠的。

11.5 trim

set 和 where都可以用trim定义

set就是

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

where就是

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

还可以自定义功能:

<trim prefix="WHERE" prefixOverrides="AND |OR " suffix="666" suffixOverrides=",">
  ...
</trim>

这样写意给sql拼接where,将最前面的and 或者 or去掉 ,将最后的逗号去掉,然后再拼接上666

了解即可。

11.6 foreach

先看官方示例:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

先拼接open字符串,然后将list集合里面的所有item拼接进来,通过分隔符逗号分隔,然后结尾为close字符串也就是扩号。index就是每个item的下标。

实例:

写个接口方法,准备查询id 为 1~3 的Blog。记得先去把表中找三个记录,把它们的uuid改成1,2,3中的一个。

List<Blog> getSomeBlogs(Map map);

不知道传什么,就传map,万能。

sql:

<select id="getSomeBlogs" resultType="blog">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id"  open="and (" close=")" separator="or">
            id = #{id}
        </foreach>
    </where>
</select>

解释一下:

查询不必多说,foreach还是在拼接sql,将传进来的map中key为ids的集合遍历,将每一个元素叫做id,开始为and ( 中间的每一个条件用 or 分隔,结尾是 )。

拼出来大概就长这样

select * from mybatis.blog WHERE ( id = ? or id = ? or id = ? )

测试一下:

@Test
public void test() {
    SqlSession sqlSession = MyUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    
    Map map = new HashMap();
    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(2);
    ids.add(3);
    map.put("ids", ids);

    System.out.println(mapper.getSomeBlogs(map));
    
    sqlSession.close();
}

11.7 SQL代码片段

有点封装的感觉,可以写一段sql语句作为公用sql,需要时就调用。

例:

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

<update id="updateBlogs" parameterType="map">
    update mybatis.blog
    <set>
        <include refid="example"/>
    </set>
    <where>
        id = #{id}
    </where>
</update>

定义时用sql标签定义,使用时用include标签引用,了解即可。

十二、缓存

缓存就是临时存储,可以提高数据交互速度。

MyBatis 在执行一次SQL查询或者SQL更新之后,这条SQL语句并不会消失,而是被MyBatis 缓存起来,当再次执行相同SQL语句的时候,就会直接从缓存中进行提取,而不是再次执行SQL命令。. MyBatis中的缓存分为一级缓存和二级缓存

测试环境搭建

一张user表,长这样

在这里插入图片描述

随便添加点数据进去。

然后接口和sql:

import org.apache.ibatis.annotations.Param;
import vip.yangsf.entity.User;

import java.util.List;
import java.util.Map;

public interface UserMapper1 {
    List<User> getAll();

    User queryUserById(@Param("id") int id);

    int addUser(Map map);
}

sql:

<?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="vip.yangsf.dao.UserMapper1">
    <select id="getAll" resultType="vip.yangsf.entity.User">
        select * from mybatis.user
    </select>

    <select id="queryUserById" resultType="User">
        select * from mybatis.user
        <where>
            id = #{id}
        </where>
    </select>

    <insert id="addUser">
        insert into mybatis.user(name, pwd) VALUES (#{name}, #{pwd})
    </insert>
</mapper>

12.1 一级缓存

一级缓存又被称为 SqlSession 级别的缓存。

同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况先, 只执行一次 SQL 语句(如果缓存没有过期)。

一级缓存是默认开启的且关不掉

看代码更容易理解:

    @Test
    public void test() {
        SqlSession sqlSession = MyUtil.getSqlSession();
        UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);

        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
        
        System.out.println("==================================");
        
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);

        System.out.println(user1 == user2);
        sqlSession.close();
    }

查看日志会发现:

在这里插入图片描述

只有一次会话,而且user1和user2是同一个东西(输出了true)。

除了查询以外的其他操作会刷新缓存:

    @Test
    public void test() {
        SqlSession sqlSession = MyUtil.getSqlSession();
        UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);

        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
        
        System.out.println("==================================");
        
        Map map = new HashMap();
        map.put("name", "qqq");
        map.put("pwd", "877602782");
        mapper.addUser(map);
        
        System.out.println("==================================");

        User user2 = mapper.queryUserById(1);
        System.out.println(user2);

        System.out.println(user1 == user2);
        sqlSession.close();
    }

发生了三次会话并且 user1和user2不是同一个东西(输出了false):

在这里插入图片描述

也可以手动清除缓存:

    @Test
    public void test() {
        SqlSession sqlSession = MyUtil.getSqlSession();
        UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);

        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
        
        System.out.println("==================================");
        // 清除缓存
        sqlSession.clearCache();
        
        User user2 = mapper.queryUserById(1);
        System.out.println(user2);

        System.out.println(user1 == user2);
        sqlSession.close();
    }

两次会话,并且两个user不是同一个(输出了false):

在这里插入图片描述

12.2 二级缓存

二级缓存存在于 SqlSessionFactory 生命周期中。二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。

看代码更好理解:

    @Test
    public void test02() {
        SqlSession sqlSession = MyUtil.getSqlSession();
        UserMapper1 mapper = sqlSession.getMapper(UserMapper1.class);

        SqlSession sqlSession2 = MyUtil.getSqlSession();
        UserMapper1 mapper2 = sqlSession2.getMapper(UserMapper1.class);

        User user1 = mapper.queryUserById(1);
        System.out.println(user1);

        sqlSession.close();
        System.out.println("==================================");

        User user2 = mapper2.queryUserById(1);
        System.out.println(user2);

        System.out.println(user1 == user2);

        sqlSession2.close();
    }
}

如果不开二级缓存,结果就是这样:

在这里插入图片描述

开启二级缓存:

  1. mybatis-config.xml里面添加配置:
<setting name="cacheEnabled" value="true"/>

默认就是开启的。

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

    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    
  2. 测试:

    同样的代码,看结果:

    在这里插入图片描述

    第二次查询直接就在缓存中取了,并且两个user相同(输出true)

当然 也可以不用设置参数:

上面的第二步变为:

<cache/>

并且要给实体类实现序列化接口

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private int id;
    private String name;
    private String pwd;
}

测试:

在这里插入图片描述

第二次查询是从缓存中查,但是输出为false

只需要将缓存设置为只读:

<cache
        readOnly="true"/>

在这里插入图片描述

完美。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值