目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

Mybatis Reference

官网

log4j

src/main/resources/log4j.properties

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=debug, stdout

依赖

<!-- 分页助手 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

Mybatis执行流程

基本执行流程

快速入门

  1. 创建mybatis_db数据库和user表
  2. 创建项目,导入依赖
  3. 创建User实体类
  4. 编写映射文件UserMapper.xml
  5. 编写核心文件SqlMapConfig.xml
  6. 编写测试类

创建数据及user表

CREATE DATABASE `mybatis_db`; USE `mybatis_db`; 

CREATE TABLE `user` ( `id` int(11) NOT NULL auto_increment, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` datetime default NULL COMMENT '生日', `sex` char(1) default NULL COMMENT '性别', `address` varchar(256) default NULL COMMENT '地址',PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (1,'子 慕','2020-11-11 00:00:00','男','北京海淀'),(2,'应颠','2020-12-12 00:00:00','男','北 京海淀');

创建Maven工程,导入依赖(Mysql驱动、mybatis、junit)

<!--指定编码及版本-->
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    <java.version>11</java.version>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
    <!-- 引入mybatis依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.4</version>
    </dependency>

    <!-- 引入mysql依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <!-- 引入JUnit依赖-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

</dependencies>

编写User实体类

package com.soulboy;

import java.util.Date;

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
  
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

编写UserMapper.xml映射配置文件(ORM思想)

src/main/resources/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命名空间: 与ID属性共同构成唯一标识 user.findAll
resultType结果类型: 返回结果类型(自动映射封装):要封装的实体的全路径
-->
<mapper namespace="userMapper">
    <!-- 查询所有 -->
    <select id="findAll" resultType="com.soulboy.domain.User">
        select * from user
    </select>

    <!-- 新增用户  #{}是Mybatis中的占位符,等同于JDBC中的? -->
    <insert id="saveUser" parameterType="com.soulboy.domain.User">
        insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
    </insert>

    <!-- 更新用户 -->
    <update id="updateUser" parameterType="com.soulboy.domain.User">
        update user set username = #{username},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id}
    </update>

    <!-- 删除用户 -->
    <delete id="deleteUser" parameterType="java.lang.Integer">
        delete from user where id = #{abc}
    </delete>
</mapper>

编写MyBatis核心配置文件

1、数据库环境配置 src/main/resources/sqlMapConfig.xml

2、映射关系配置的引入(引入映射配置文件的路径)

<?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">
            <!-- 当前的事务交由JDBC进行管理 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 数据源信息 POOLED:使用Mybatis的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:50000/MyBatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 引入映射配置文件 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>
</configuration>

编写测试类

src/main/java/com/soulboy/test/MybatisTest.java

package com.soulboy.test;

import com.soulboy.domain.User;
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 org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;

public class MybatisTest {
    /*
        快速入门测试方法
    */
    @Test
    public void mybatisQuickStart() throws IOException {
        //1.加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.获取sqlSessionFactory工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.获取sqlSession会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.执行sql 参数:statementid:namespace.id
        List<User> users = sqlSession.selectList("userMapper.findAll");
        //5.遍历打印查询结果集
        for (User user : users) {
            System.out.println(user);
        }
        //6.关闭资源
        sqlSession.close();
    }

    /*
        测试新增用户
    */
    @Test
    public void testSave() throws IOException {
        //1.加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.获取sqlSessionFactory工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.获取sqlSession会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.执行sql 参数:statementid:namespace.id
        User user = new User();
        user.setUsername("高中美");
        user.setBirthday(new Date());
        user.setSex("女");
        user.setAddress("河南开封");
        sqlSession.insert("userMapper.saveUser", user);
        //5.关闭资源
        sqlSession.commit();
        sqlSession.close();
    }

    /*
        测试更新用户
    */
    @Test
    public void testUpdate() throws IOException {
        //1.加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.获取sqlSessionFactory工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.获取sqlSession会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.执行sql 参数:statementid:namespace.id
        User user = new User();
        user.setId(3);
        user.setUsername("高中直");
        user.setBirthday(new Date());
        user.setSex("男");
        user.setAddress("河南开封");
        sqlSession.update("userMapper.updateUser", user);
        //5.关闭资源
        sqlSession.commit();
        sqlSession.close();
    }

    /*
           测试更新用户
       */
    @Test
    public void testDelete() throws IOException {
        //1.加载核心配置文件
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.获取sqlSessionFactory工厂对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.获取sqlSession会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.执行sql 参数:statementid:namespace.id
        sqlSession.delete("userMapper.deleteUser", 3);
        //5.关闭资源
        sqlSession.commit();
        sqlSession.close();
    }
}

DAO层传统开发方式:接口

需要手动编写接口、实现 类、XXXMapper.xml文件

缺点
Impl实现类中模板代码冗余
Impl实现类中硬编码:List users = sqlSession.selectList("userMapper.findAll")
  1. 接口
    com/soulboy/dao/IUserDao.java
package com.soulboy.dao;

import com.soulboy.domain.User;
import java.io.IOException;
import java.util.List;

public interface IUserDao {
/**

* 查询所有
  */
  public List<User> findAll() throws IOException;
  }
  1. 实现类
    com/soulboy/dao/impl/UserDaoImpl.java
public class UserDaoImpl implements IUserDao {

	@Override
	public List<User> findAll() throws IOException {
		InputStream resourceAsStream = 				Resources.getResourceAsStream("sqlMapConfig.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
		SqlSession sqlSession = sqlSessionFactory.openSession();
		List`<User>` users = sqlSession.selectList("userMapper.findAll");
		return users;
	}

}
  1. 映射文件
    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命名空间: 与ID属性共同构成唯一标识 user.findAll
resultType结果类型: 返回结果类型(自动映射封装):要封装的实体的全路径
typeAliases别名: com.soulboy.domain.User
-->

<mapper namespace="userMapper">
    <!-- 查询所有 -->
    <select id="findAll" resultType="user">
        select * from user
    </select>
</mapper>
  1. 测试代码
public class MybatisTest {
    /**
     * mybatis的dao层传统开发方式测试
    */
    @Test
    public void Test1() throws IOException {
        //调用持久层方法
        IUserDao userDao = new UserDaoImpl();
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
    }
}

DAO层代理开发方式:接口代理

基于接口代理实现持久层是企业的主流开发方式

开发规范
基于接口代理

开发规范
接口代理底层原理

  1. 接口
    com/soulboy/mapper/UserMapper.java
public interface UserMapper {
    /**
     * 根据id查询用户
     */
    public User findUserById(int id);

}
  1. 映射文件
    com/soulboy/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">
<!--  -->
<mapper namespace="com.soulboy.mapper.UserMapper">
    <select id="findUserById" parameterType="int" resultType="user">
        select * from user where id = #{id}
    </select>
</mapper>
  1. sqlMapConifg.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文件 -->
    <properties resource="jdbc.properties"></properties>
    <!-- 设置别名 -->
    <typeAliases>
        <!-- 方式一: 给单个实体起别名 -->
        <!-- <typeAlias type="com.soulboy.domain.User" alias="user"></typeAlias> -->
        <!-- 方式二: 批量起别名 -->
        <package name="com.soulboy.domain"/>
    </typeAliases>
    <!-- 运行环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 当前的事务交由JDBC进行管理 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 数据源信息 POOLED:使用Mybatis的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 引入映射配置文件 -->
    <mappers>
        <!-- 使用该方式: XML文件的全路径-->
        <!--<mapper resource="com.soulboy/mapper/UserMapper.xml"></mapper>-->

        <!-- 使用该方式: 接口和映射文件需要同包同名 -->
        <!--<mapper class="com.soulboy.mapper.UserMapper"></mapper>-->

        <!-- 批量加载映射: Mapper接口的包路径 -->
         <package name="com.soulboy.mapper"/>
    </mappers>
</configuration>

src/main/resources/jdbc.properties 文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:50000/MyBatis?useSSL=false
jdbc.username=root
jdbc.password=123456
  1. 测试类
public class MybatisTest {
    /**
     * mybatis的dao层传统开发方式测试
    */
    @Test
    public void Test1() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.findUserById(1);
        System.out.println(user);
    }
}

高级查询

ResultType、ResultMap

属性名功能
ResultType如果实体的属性名与表中字段名一致,将查询结果自动封装到实体类中
ResultMap如果实体的属性名与表中字段名不一致,可以使用ResutlMap实现手动封装到实体类中

当实体属性名与数据库表中字段名不一致的时候,可以采用ResultMap解决。

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

<mapper namespace="com.soulboy.mapper.UserMapper">
    <!-- resultMap:手动配置实体属性与表中字段的映射关系,完成手动封装
        id: 标签的唯一标识
        type: 封装后实体类型
        property:实体的属性名
        column: 表中字段的属性名
        <id>: 主键的封装
        <result>: 表中普通字段的封装
     -->
    <resultMap id="userResultMap" type="com.soulboy.domain.User">
        <id property="id" column="id"></id>
        <result property="usernameabc" column="username"></result>
        <result property="birthdayabc" column="birthday"></result>
        <result property="sexabc" column="sex"></result>
        <result property="addressabc" column="address"></result>
    </resultMap>

    <!-- 查询指定id的用户 -->
    <select id="findUserById" parameterType="int" resultMap="userResultMap">
        select * from user where id = #{id}
    </select>

    <!-- 查询所有用户 -->
    <select id="findAllResultMap" resultMap="userResultMap">
        select * from user
    </select>
</mapper>

多条件查询

根据id和username查询

方式一:使用 #{arg0}~#{argn} 或者 #{param1}~#{paramn} 获取参数

UserMapper接口

/**
     * 根据id和username查询user表
     */
    public List<User>  findByIdAndUserName1(int id, String username);

UserMapper.xml

<!-- 多条件查询: 方式一 -->
    <select id="findByIdAndUserName1" resultMap="userResultMap" >
        select * from user where id = #{arg0} and username = #{arg1}
    </select>

测试类

/**
     *
     * 多条件查询: 方式一
     * @throws IOException
     */
    @Test
    public void Test3() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findByIdAndUserName1(5, "高中美");
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

方式二:注解,引入 @Param() 注解获取参数
UserMapper接口

/**
     * 根据id和username查询user表:方式二
     */
    public List<User>  findByIdAndUserName2(@Param("id") int id, @Param("username") String username);

UserMapper.xml

<!-- 多条件查询: 方式二 -->
    <select id="findByIdAndUserName2" resultMap="userResultMap" >
        select * from user where id = #{id} and username = #{username}
    </select>

测试类

/**
     *
     * 多条件查询: 方式二
     * @throws IOException
     */
    @Test
    public void Test4() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findByIdAndUserName1(5, "高中美");
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

方式三:pojo对象传递参数
UserMapper接口

/**
     * 根据id和username查询user表:方式三
     */
    public List<User>  findByIdAndUserName3(User user);

UserMapper.xml

<!-- 多条件查询: 方式三 -->
    <select id="findByIdAndUserName3" resultMap="userResultMap" parameterType="user">
        select * from user where id = #{id} and username = #{usernameabc}
    </select>

测试类

/**
     *
     * 多条件查询: 方式三
     * @throws IOException
     */
    @Test
    public void Test5() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user1 = new User();
        user1.setId(5);
        user1.setUsernameabc("高中美");

        List<User> users = userMapper.findByIdAndUserName3(user1);

        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

模糊查询

名称功能
${}1. 通过 #{} 可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。2. #{} 可以接收简单类型值或pojo属性值。 3. 如果parameterType传输单个简单类型值, #{} 括号中名称随便写。
#{}1. 通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换,会出现sql注入问题。2.${} 可以接收简单类型值或pojo属性值。3.如果parameterType传输单个简单类型值, ${} 括号中只能是value。

根据username 模糊查询 user表

方式一
UserMapper接口

/**
     * 模糊查询:方式一
     * @param username
     * @return
     */
    public List<User> findByUsername1(String username);

UserMapper.xml

<!-- 模糊查询: 方式一  其实可以随便写-->
    <select id="findByUsername1" resultMap="userResultMap" parameterType="string">
        <!-- #{}在mybatis中是占位符,引用参数值的时候会自动添加单引号 -->
        select * from user where username like #{usernameabc}
    </select>

测试类

/**
     *
     * 模糊查询: 方式一
     * @throws IOException
     */
    @Test
    public void Test6() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findByUsername1("%中%");
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

方式二
UserMapper 接口

/**
     * 模糊查询:方式二
     * @param username
     * @return
     */
    public List<User> findByUsername2(String username);

UserMapper.xml

<!-- 模糊查询: 方式二  -->
    <select id="findByUsername2" resultMap="userResultMap" parameterType="string">
        <!-- parameterType是基本数据类型或者String的时候,${}里面的值只能写value
            ${}:sql原样拼接
         -->
        select * from user where username like '${value}'
    </select>

测试类

/**
     *
     * 模糊查询: 方式二
     * @throws IOException
     */
    @Test
    public void Test7() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findByUsername2("%中%");
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

映射文件深入

返回主键

我们很多时候有这种需求,向数据库插入一条记录后,希望能立即拿到这条记录在数据库中的主键值。

方式一:useGeneratedKeys
注意:只适用于主键自增的数据库,mysql和sqlserver支持,oracle不行。

UserMapper接口

/**
     * 添加用户并获取返回主键:方式一
     *
     * @param
     * @return
     */
    public void saveUser(User user);

UserMapper映射文件

<insert id="saveUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
        insert into user(username,birthday,sex,address) values(#{usernameabc},#{birthdayabc},#{sexabc},#{addressabc})
    </insert>

测试类

/**
     *
     * 添加用户并返回主键: 方式一
     * @throws IOException
     */
    @Test
    public void Test8() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setUsernameabc("高中雅");
        user.setBirthdayabc(new Date());
        user.setAddressabc("河南开封");
        user.setSexabc("女");
        System.out.println(user); //User{id=null, usernameabc='高中雅', birthdayabc=Fri Oct 28 14:42:08 CST 2022, sexabc='女', addressabc='河南开封'}
        userMapper.saveUser(user);
        System.out.println(user); //User{id=6, usernameabc='高中雅', birthdayabc=Fri Oct 28 14:42:08 CST 2022, sexabc='女', addressabc='河南开封'}
	sqlSession.commit();
        sqlSession.close();
    }

方式二:selectKey

UserMapper接口

/**
     * 添加用户并获取返回主键:方式二
     *
     * @param
     * @return
     */
    public void saveUser2(User user);

UserMapper映射文件

<!-- 添加用户并返回主键: 方式二
        selectKey支持所有类型的数据库,适用范围更广.
        order: 支持主键自增的用AFTER(mysql、sqlserver),不支持主键自增的用BEFORE(oracle)
        keyColumn: 指定表中主键字段名
        keyProperty: 把返回主键的值,封装到实体中的指定属性
        resultType: 指定主键类型
    -->
    <insert id="saveUser2" parameterType="user" >
        <selectKey order="AFTER" keyColumn="id" keyProperty="id" resultType="int">
            select LAST_INSERT_ID();
        </selectKey>
        insert into user(username,birthday,sex,address) values(#{usernameabc},#{birthdayabc},#{sexabc},#{addressabc})
    </insert>

测试类

/**
     *
     * 添加用户并返回主键: 方式二
     * @throws IOException
     */
    @Test
    public void Test9() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setUsernameabc("妞妞");
        user.setBirthdayabc(new Date());
        user.setAddressabc("河南开封");
        user.setSexabc("女");
        System.out.println(user); //User{id=null, usernameabc='妞妞', birthdayabc=Fri Oct 28 15:06:13 CST 2022, sexabc='女', addressabc='河南开封'}
        userMapper.saveUser2(user);
        System.out.println(user); //User{id=10, usernameabc='妞妞', birthdayabc=Fri Oct 28 15:06:13 CST 2022, sexabc='女', addressabc='河南开封'}
        sqlSession.commit();
        sqlSession.close();
    }

动态SQL之 if

根据id和username查询,但是不确定两个都有值

UserMapper接口

/**
     * 动态sql的if标签: 多条件查询
     * @param
     * @return
     */
    public List<User> findByUsernameIf(User user);

UserMapper映射文件

<!-- 动态sql之if: 多条件查询 -->
    <select id="findByUsernameIf" parameterType="user" resultMap="userResultMap">
        select * from user
        <where>
            <!-- test里面写的就是表达式 -->
            <if test="id != null">
                and id = #{id}
            </if>
            <if test="usernameabc != null">
                and username like #{usernameabc}
            </if>
        </where>
    </select>

测试类

/**
     *
     * 动态sql之if: 多条件查询 
     * @throws IOException
     */
    @Test
    public void Test10() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user = new User();
        //user.setId(8);
        user.setUsernameabc("%妞%");

        List<User> users = userMapper.findByUsernameIf(user);
        for (User user1 : users) {
            System.out.println(user1);  //User{id=10, usernameabc='妞妞', birthdayabc=Fri Oct 28 15:09:52 CST 2022, sexabc='女', addressabc='河南开封'}

        }
        sqlSession.close();
    }

动态SQL之 set

动态更新user表数据,如果该属性有值就更新,没有值不做处理。

UserMapper接口

/**
     * 动态sql的set标签: 动态更新
     * @param
     * @return
     */
    public void updateIf(User user);

UserMapper映射文件

<!-- 动态sql的set标签: 动态更新 -->
    <select id="updateIf" parameterType="user">
        update user
        <!-- <set>: 在更新时会自动添加set关键字,还会去掉最后一个条件的逗号 -->
        <set>
            <if test="usernameabc != null">
                username = #{usernameabc},
            </if>
            <if test="birthdayabc != null">
                birthday = #{birthdayabc},
            </if>
            <if test="sexabc != null">
                sex = #{sexabc},
            </if>
            <if test="addressabc != null">
                address = #{addressabc},
            </if>
        </set>
        where id = #{id}
    </select>

测试类

/**
     *
     * 动态sql的set标签: 动态更新
     * @throws IOException
     */
    @Test
    public void Test11() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        User user = new User();
        user.setId(12);
        user.setUsernameabc("开封第一猛男");
        userMapper.updateIf(user);
        sqlSession.commit();
        sqlSession.close();
    }

动态SQL之 foreach

foreach主要是用来做数据的循环遍历
例如: select * from user where id in (1,2,3) 在这样的语句中,传入的参数部分必须依靠foreach遍历才能实现。

* <foreach>标签用于遍历集合,它的属性:
	• collection:代表要遍历的集合元素
	• open:代表语句的开始部分
	• close:代表结束部分
	• item:代表遍历集合的每个元素,生成的变量名
	• sperator:代表分隔符
集合

UserMapper接口

/**
     * 动态sql的foreach标签:根据多个id值查询用户(集合)
     */
    public List<User> findByList(List<Integer> ids);

UserMapper映射文件

<!-- 动态sql的foreach标签(集合):根据多个id值查询用户
        List集合的别名: list
    -->
    <select id="findByList" parameterType="list" resultMap="userResultMap">
        select * from user
        <where>
            <!-- collection: 代表要遍历的集合元素,collection或list(list集合元素如果是基本数据类型的话)
                 open: 代表语句的开始部分
                 close: 代表语句的结束部分
                 item: 代表遍历集合中的每一个元素
                 separator: 分隔符
            -->
            <foreach collection="collection" open="id in (" close=")" item="element" separator=",">
                #{element}
            </foreach>
        </where>
    </select>

测试类

/**
     *
     * 动态sql的foreach标签(集合):根据多个id值查询用户
     * @throws IOException
     */
    @Test
    public void Test12() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        ArrayList<Integer> ids = new ArrayList<>();
        ids.add(1);
        ids.add(2);
        ids.add(10);
        ids.add(12);
        List<User> users = userMapper.findByList(ids);
        for (User user : users) {
            System.out.println(user);
//            User{id=1, usernameabc='子 慕', birthdayabc=Wed Nov 11 00:00:00 CST 2020, sexabc='男', addressabc='北京海淀'}
//            User{id=2, usernameabc='应颠', birthdayabc=Sat Dec 12 00:00:00 CST 2020, sexabc='男', addressabc='北京海淀'}
//            User{id=10, usernameabc='妞妞', birthdayabc=Fri Oct 28 15:09:52 CST 2022, sexabc='女', addressabc='河南开封'}
//            User{id=12, usernameabc='开封第一猛男', birthdayabc=Fri Oct 28 16:44:54 CST 2022, sexabc='男', addressabc='河南开封'}
        }
        sqlSession.close();
    }
数组

UserMapper接口

/**
     * 动态sql的foreach标签:根据多个id值查询用户(数组)
     */
    public List<User> findByArray(Integer[] ids);

UserMapper映射文件

<!-- 动态sql的foreach标签(数组):根据多个id值查询用户
        Integer别名: int
    -->
    <select id="findByArray" parameterType="int" resultMap="userResultMap">
        select * from user
        <where>
            <!-- collection: 代表要遍历的集合元素,collection或list(list集合元素如果是基本数据类型的话)
                 open: 代表语句的开始部分
                 close: 代表语句的结束部分
                 item: 代表遍历集合中的每一个元素
                 separator: 分隔符
            -->
            <foreach collection="array" open="id in (" close=")" item="element" separator=",">
                #{element}
            </foreach>
        </where>
    </select>

测试类

/**
     *
     * 动态sql的foreach标签(数组):根据多个id值查询用户
     * @throws IOException
     */
    @Test
    public void Test13() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        Integer[] ids = {1,2,10,12};
        List<User> users = userMapper.findByArray(ids);
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

sql片段

用于抽取重复的sql语句

UserMapper映射文件

<!-- 抽取sql片段 -->
    <sql id="selectUser">
        select * from user
    </sql>
    
    <!-- 动态sql的foreach标签(集合):根据多个id值查询用户
        List集合的别名: list
    -->
    <select id="findByList" parameterType="list" resultMap="userResultMap">
        <include refid="selectUser"/>
        <where>
            <!-- collection: 代表要遍历的集合元素,collection或list(list集合元素如果是基本数据类型的话)
                 open: 代表语句的开始部分
                 close: 代表语句的结束部分
                 item: 代表遍历集合中的每一个元素
                 separator: 分隔符
            -->
            <foreach collection="collection" open="id in (" close=")" item="element" separator=",">
                #{element}
            </foreach>
        </where>
    </select>

    <!-- 动态sql的foreach标签(数组):根据多个id值查询用户
        Integer别名: int
    -->
    <select id="findByArray" parameterType="int" resultMap="userResultMap">
        <include refid="selectUser"/>
        <where>
            <!-- collection: 代表要遍历的集合元素,collection或list(list集合元素如果是基本数据类型的话)
                 open: 代表语句的开始部分
                 close: 代表语句的结束部分
                 item: 代表遍历集合中的每一个元素
                 separator: 分隔符
            -->
            <foreach collection="array" open="id in (" close=")" item="element" separator=",">
                #{element}
            </foreach>
        </where>
    </select>

核心配置文件

常用标签功能
properties加载外部properties文件
typeAliases设置类型别名(Mybatis默认设置了很多基本数据类型的别名,例如:Integer => int)
environments配置数据源
plugins引入第三方插件

plugins标签

Mybatis可以使用第三方的插件来对功能进行扩展。

PageHelper

分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。

  1. 导入用于PageHelper依赖的Maven坐标
<!-- 分页助手 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>3.7.5</version>
        </dependency>
        <dependency>
            <groupId>com.github.jsqlparser</groupId>
            <artifactId>jsqlparser</artifactId>
            <version>0.9.1</version>
        </dependency>
  1. 在mybatis核心配置文件中配置PageHelper插件(注意:<plugins>标签的位置在properties、typeAliases标签之后。)
<!-- 引入第三方插件 -->
    <plugins>
        <!-- 分页助手的插件 -->
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <!-- dialect(指定方言): 不同数据库分页语法不一样,mysql分页用limit -->
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>
  1. 测试分页
/**
     *
     * 测试分页PageHelper
     * @throws IOException
     */
    @Test
    public void Test14() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //设置分页信息  startPage(pageNum,pageSize)
        PageHelper.startPage(1, 2);

        List<User> users = userMapper.findAllResultMap();
        for (User user : users) {
            System.out.println(user);
        }

        //获取分页相关信息
        PageInfo<User> pageInfo = new PageInfo<User>(users);
        System.out.println("总条数:"+pageInfo.getTotal());
        System.out.println("总页数:"+pageInfo.getPages());
        System.out.println("当前页:"+pageInfo.getPageNum());
        System.out.println("每页显示长度:"+pageInfo.getPageSize());
        System.out.println("是否第一页:"+pageInfo.isIsFirstPage());
        System.out.println("是否最后一页:"+pageInfo.isIsLastPage());

        //关闭数据库连接
        sqlSession.close();
    }

多表查询

数据关系及配置总结
多对一(一对一)配置使用resultMap + assocication 做配置
一对多配置使用resultMap + collection 做配置
多对多使用resultMap + collection 做配置 (多对多的配置跟一对多很相似,难度在于SQL语句的编写)

实体之间关系

  • 一对一(人:身份证)
  • 一对多(用户:订单)
  • 多对多(学生:课程)

环境准备

DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`ordertime` VARCHAR(255) DEFAULT NULL,
`total` DOUBLE DEFAULT NULL,
`uid` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `uid` (`uid`),
CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders` VALUES ('1', '2020-12-12', '3000', '1');
INSERT INTO `orders` VALUES ('2', '2020-12-12', '4000', '1');
INSERT INTO `orders` VALUES ('3', '2020-12-12', '5000', '2');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`rolename` VARCHAR(255) DEFAULT NULL,
`roleDesc` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', 'CTO', 'CTO');
INSERT INTO `sys_role` VALUES ('2', 'CEO', 'CEO');
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`userid` INT(11) NOT NULL,
`roleid` INT(11) NOT NULL,
PRIMARY KEY (`userid`,`roleid`),
KEY `roleid` (`roleid`),
CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `sys_role`
(`id`),
CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `user`
(`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('2', '1');
INSERT INTO `sys_user_role` VALUES ('1', '2');
INSERT INTO `sys_user_role` VALUES ('2', '2');

一对一

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对一查询的需求:查询所有订单,与此同时查询出每个订单所属的用户

Bean
com.soulboy.domain.User

package com.soulboy.domain;

import java.util.Date;

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

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

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

com.soulboy.domain.Order

package com.soulboy.domain;

public class Order {
    private Integer id;
    private String ordertime;
    private Double total;
    private Integer uid;

    //表示当前订单属于哪个用户
    private User user;


    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", ordertime='" + ordertime + '\'' +
                ", total=" + total +
                ", uid=" + uid +
                ", user=" + user +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getOrdertime() {
        return ordertime;
    }

    public void setOrdertime(String ordertime) {
        this.ordertime = ordertime;
    }

    public Double getTotal() {
        return total;
    }

    public void setTotal(Double total) {
        this.total = total;
    }

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

UserMapper接口

package com.soulboy.mapper;

import com.soulboy.domain.Order;

import java.util.List;

public interface OrderMapper {
    /*
        一对一关联查询:查询所有订单,与此同时还要查询出每个订单所属的用户信息
     */
    public List<Order> findAllOrderWithUser();
}

OrderMapper映射文件

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

<mapper namespace="com.soulboy.mapper.OrderMapper">
    <!-- resultMap:手动配置实体属性与表中字段的映射关系,完成手动封装
        id: 标签的唯一标识
        type: 封装后实体类型
        property:实体的属性名
        column: 表中字段的属性名
        <id>: 主键的封装
        <result>: 表中普通字段的封装
     -->
    <resultMap id="orderMap" type="com.soulboy.domain.Order">
        <id property="id" column="id"></id>
        <result property="ordertime" column="ordertime"></result>
        <result property="total" column="total"></result>
        <result property="uid" column="uid"></result>
        <!-- association:在进行一对一关联查询配置时,使用association标签进行关联
                property: 实体的属性名称
                javaType: 属性类型
        -->
        <association property="user" javaType="com.soulboy.domain.User">
            <id property="id" column="uid"/>
            <result property="username" column="username"/>
            <result property="birthday" column="birthday"/>
            <result property="sex" column="sex"/>
            <result property="address" column="address"/>
        </association>
    </resultMap>

    <!-- 一对一关联查询:查询所有订单,与此同时还要查询出每个订单所属的用户信息 -->
    <select id="findAllOrderWithUser" resultMap="orderMap">
        select * from orders,user where orders.uid = user.id
        <!-- select * from orders o left join user u on o.uid = u.id -->
    </select>

</mapper>

测试类

package com.soulboy.test;


import com.soulboy.domain.Order;
import com.soulboy.mapper.OrderMapper;
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 org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisTest {
    /**
     * 一对一关联查询:查询所有订单,与此同时还要查询出每个订单所属的用户信息
    */
    @Test
    public void Test1() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
        List<Order> orders = orderMapper.findAllOrderWithUser();
        for (Order order : orders) {
            System.out.println(order);
        }
        sqlSession.close();
    }
}

一对多

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询所有用户,与此同时查询出该用户具有的订单

select * from orders,user where orders.uid = user.id
select u.*,o.id as oid,o.ordertime,o.uid from orders o right join user u on o.uid = u.id

Bean
com.soulboy.domain.User

package com.soulboy.domain;

import java.util.Date;
import java.util.List;

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    //表示多方关系: 集合
    private List<Order> ordersList;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                ", ordersList=" + ordersList +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public List<Order> getOrdersList() {
        return ordersList;
    }

    public void setOrdersList(List<Order> ordersList) {
        this.ordersList = ordersList;
    }
}

com.soulboy.domain.Order

package com.soulboy.domain;

import java.util.Date;
import java.util.List;

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    //表示多方关系: 集合
    private List<Order> ordersList;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                ", ordersList=" + ordersList +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public List<Order> getOrdersList() {
        return ordersList;
    }

    public void setOrdersList(List<Order> ordersList) {
        this.ordersList = ordersList;
    }
}

com.soulboy.mapper.UserMapper

package com.soulboy.mapper;


import com.soulboy.domain.User;
import java.util.List;

public interface UserMapper {
    /*
        一对多查询的需求:查询所有用户,与此同时查询出该用户具有的订单
     */
    public List<User> findAllUserWithOrder();
}

com/soulboy/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">

<mapper namespace="com.soulboy.mapper.UserMapper">
    <!-- resultMap:手动配置实体属性与表中字段的映射关系,完成手动封装
        id: 标签的唯一标识
        type: 封装后实体类型
        property:实体的属性名
        column: 表中字段的属性名
        <id>: 主键的封装
        <result>: 表中普通字段的封装
     -->
    <resultMap id="userMap" type="com.soulboy.domain.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <!-- collection:一对多查询的需求:查询所有用户,与此同时查询出该用户具有的订单
                property: 实体的属性名称
                javaType: 属性类型
        -->
        <collection property="ordersList" ofType="com.soulboy.domain.Order">
            <id property="id" column="oid"></id>
            <result property="ordertime" column="ordertime"/>
            <result property="total" column="total"/>
            <result property="uid" column="uid"/>
        </collection>
    </resultMap>
    <!-- 一对多查询的需求:查询所有用户,与此同时查询出该用户具有的订单 -->
    <select id="findAllUserWithOrder" resultMap="userMap">
        select u.*,o.id oid,o.ordertime,o.total,o.uid from orders o right join user u on o.uid = u.id
        <!-- select * from orders o left join user u on o.uid = u.id -->
    </select>
</mapper>

测试类

/**
     * 一对多查询的需求:查询所有用户,与此同时查询出该用户具有的订单
     */
    @Test
    public void Test2() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findAllUserWithOrder();
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
//        User{id=1, username='子 慕', birthday=Wed Nov 11 00:00:00 CST 2020, sex='男', address='北京海淀', ordersList=[Order{id=1, ordertime='2020-12-12', total=3000.0, uid=1, user=null}, Order{id=2, ordertime='2020-12-12', total=4000.0, uid=1, user=null}]}
//        User{id=2, username='应颠', birthday=Sat Dec 12 00:00:00 CST 2020, sex='男', address='北京海淀', ordersList=[Order{id=3, ordertime='2020-12-12', total=5000.0, uid=2, user=null}]}
//        User{id=5, username='高中美', birthday=Sun Oct 23 18:04:52 CST 2022, sex='女', address='河南开封', ordersList=[]}
//        User{id=8, username='高中雅', birthday=Fri Oct 28 15:07:50 CST 2022, sex='女', address='河南开封', ordersList=[]}
//        User{id=10, username='妞妞', birthday=Fri Oct 28 15:09:52 CST 2022, sex='女', address='河南开封', ordersList=[]}
//        User{id=11, username='超蛋', birthday=Fri Oct 28 16:39:31 CST 2022, sex='男', address='河南开封', ordersList=[]}
//        User{id=12, username='开封第一猛男', birthday=Fri Oct 28 16:44:54 CST 2022, sex='男', address='河南开封', ordersList=[]}
    }

多对多

用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询所有用户同时查询出该用户的所有角色

SELECT u.*,r.id AS rid,r.rolename,r.roleDesc FROM USER u LEFT JOIN sys_user_role ur On ur.userid=u.id LEFT JOIN sys_role r ON ur.roleid=r.id;

Bean (多对多任何一段的角度都可以,这里选择的是User)

com.soulboy.domain.User

package com.soulboy.domain;

import java.util.Date;
import java.util.List;

public class User {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;

    // 表示多方关系(一对多): 代表了当前用户所具有的订单列表 collection
    private List<Order> ordersList;

    // 表示多方关系(多对多): 代表了当前用户所具有的角色列表 collection
    private List<Role> roleList;


    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public List<Order> getOrdersList() {
        return ordersList;
    }

    public void setOrdersList(List<Order> ordersList) {
        this.ordersList = ordersList;
    }

    public List<Role> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<Role> roleList) {
        this.roleList = roleList;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", birthday=" + birthday +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                ", ordersList=" + ordersList +
                ", roleList=" + roleList +
                '}';
    }
}

com.soulboy.domain.Role

package com.soulboy.domain;

public class Role {
    private Integer id;
    private String rolename;
    private String roleDesc;

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", rolename='" + rolename + '\'' +
                ", roleDesc='" + roleDesc + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getRolename() {
        return rolename;
    }

    public void setRolename(String rolename) {
        this.rolename = rolename;
    }

    public String getRoleDesc() {
        return roleDesc;
    }

    public void setRoleDesc(String roleDesc) {
        this.roleDesc = roleDesc;
    }
}

UserMapper接口

package com.soulboy.mapper;


import com.soulboy.domain.User;
import java.util.List;

public interface UserMapper {

    /*
        多对多关联查询:查询所有用户,同时还要查询出每个用户所关联的角色信息
    */
    public List<User> findAllUserWithRole();


}

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

<mapper namespace="com.soulboy.mapper.UserMapper">
    <!-- 多对多关联查询:查询所有用户,同时还要查询出每个用户所关联的角色信息 -->
    <resultMap id="userRoleMap" type="com.soulboy.domain.User">
        <id property="id" column="id"/>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <collection property="roleList" ofType="com.soulboy.domain.Role">
            <id property="id" column="rid"></id>
            <result property="rolename" column="rolename"></result>
            <result property="roleDesc" column="roleDesc"></result>
        </collection>
    </resultMap>
    <select id="findAllUserWithRole" resultMap="userRoleMap">
        SELECT u.*,r.id AS rid,r.rolename,r.roleDesc FROM USER u LEFT JOIN sys_user_role ur On ur.userid=u.id LEFT JOIN sys_role r ON ur.roleid=r.id
    </select>
</mapper>

测试类

/**
     * 多对多关联查询:查询所有用户,同时还要查询出每个用户所关联的角色信息
     */
    @Test
    public void Test3() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findAllUserWithRole();

        for (User user : users) {
            System.out.println(user);
        }
//        User{id=1, username='子 慕', birthday=Wed Nov 11 00:00:00 CST 2020, sex='男', address='北京海淀', ordersList=null, roleList=[Role{id=1, rolename='CTO', roleDesc='CTO'}, Role{id=2, rolename='CEO', roleDesc='CEO'}]}
//        User{id=2, username='应颠', birthday=Sat Dec 12 00:00:00 CST 2020, sex='男', address='北京海淀', ordersList=null, roleList=[Role{id=1, rolename='CTO', roleDesc='CTO'}, Role{id=2, rolename='CEO', roleDesc='CEO'}]}
//        User{id=5, username='高中美', birthday=Sun Oct 23 18:04:52 CST 2022, sex='女', address='河南开封', ordersList=null, roleList=[]}
//        User{id=8, username='高中雅', birthday=Fri Oct 28 15:07:50 CST 2022, sex='女', address='河南开封', ordersList=null, roleList=[]}
//        User{id=10, username='妞妞', birthday=Fri Oct 28 15:09:52 CST 2022, sex='女', address='河南开封', ordersList=null, roleList=[]}
//        User{id=11, username='超蛋', birthday=Fri Oct 28 16:39:31 CST 2022, sex='男', address='河南开封', ordersList=null, roleList=[]}
//        User{id=12, username='开封第一猛男', birthday=Fri Oct 28 16:44:54 CST 2022, sex='男', address='河南开封', ordersList=null, roleList=[]}
    }

加载策略

实际开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息,因为一个用户可能会关联多个订单,此时就需要用到延迟加载。延迟加载是基于嵌套查询来实现的

加载策略数据关系优缺点
延迟加载一对多,多对多优点:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。 缺点:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
立即加载一对一,多对一
* 在一对多中,当我们有一个用户,它有个100个订单
	在查询用户的时候,要不要把关联的订单查出来?
	在查询订单的时候,要不要把关联的用户查出来?

* 回答
	在查询用户时,用户下的订单应该是,什么时候用,什么时候查询。
	在查询订单时,订单所属的用户信息应该是随着订单一起查询出来。

局部延迟加载

在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略

设置触发延迟加载的方法
在配置了延迟加载策略后,发现即使没有调用关联对象的任何方法,但是在你调用当前对象的equals、clone、hashCode、toString方法时也会触发关联对象的查询。
我们可以在配置文件中使用lazyLoadTriggerMethods配置项覆盖掉上面四个方法。
sqlMapConfig.xml

<configuration>
    <properties/>

    <!-- 设置触发延迟加载的方法 -->
    <settings>
        <!--所有方法都会延迟加载-->
        <setting name="lazyLoadTriggerMethods" value="toString()"/>
    </settings>

    <typeAliases/>
    <plugins/>
    <environments/>
    <mappers/>
</configuration>

需求:一对多嵌套查询(延迟加载):查询所有的用户,同时还要查询出每个用户所关联的订单信息

com.soulboy.mapper.UserMapper

public interface UserMapper {
    一对多查询的需求(嵌套查询):查询所有用户,与此同时查询出该用户具有的订单
     */
    public List<User> findAllUserWithOrder2();
}

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

<mapper namespace="com.soulboy.mapper.UserMapper">

    <!-- 一对多查询的需求(嵌套查询实现延迟加载):查询所有用户,与此同时查询出该用户具有的订单 -->
    <resultMap id="userOrderMap2" type="com.soulboy.domain.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
        <!-- collection:一对多查询的需求:查询所有用户,与此同时查询出该用户具有的订单
                property: 实体的属性名称
                javaType: 属性类型
                id: 传入select查询的参数
                fetchType="lazy" : 延迟加载策略
                fetchType="eager": 立即加载策略
        -->
        <collection property="ordersList" 
		    ofType="com.soulboy.domain.Order"
                    column="id" 
		    select="com.soulboy.mapper.OrderMapper.findByUid"
                    fetchType="lazy"></collection>
    </resultMap>

    <!-- 一对多查询的需求(嵌套查询实现延迟加载):查询所有用户,与此同时查询出该用户具有的订单 -->
    <select id="findAllUserWithOrder2" resultMap="userOrderMap2">
        SELECT * FROM USER
    </select>

</mapper>

com.soulboy.mapper.OrderMapper

public interface OrderMapper {
    /**
     * 根据用户id查询订单
     */
    public List<Order>  findByUid(Integer uid);
}

OrderMapper.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="com.soulboy.mapper.OrderMapper">

    <!-- 根据uid查询订单 -->
    <select id="findByUid" parameterType="int" resultType="com.soulboy.domain.Order">
        SELECT * FROM orders WHERE uid = #{uid}
    </select>

</mapper>

实现类

/**
     * 一对多查询的需求(嵌套查询实现延迟加载):查询所有用户,与此同时查询出该用户具有的订单
     */
    @Test
    public void Test5() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findAllUserWithOrder2();
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

全局延迟加载

在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。注意:局部的加载策略优先级高于全局的加载策略。

sqlMapConfig.xml

<!-- 设置触发延迟加载的方法 -->
    <settings>
        <!--开启全局延迟加载功能-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--所有方法都会延迟加载-->
        <setting name="lazyLoadTriggerMethods" value="toString()"/>
    </settings>

缓存

 当用户频繁查询某些固定的数据时,第一次将这些数据从数据库中查询出来,保存在缓存中。当用户再次查询这些数据时,不用再通过数据库查询,而是去缓存里面查询。减少网络连接和数据库查询带来的损耗,从而提高我们的查询效率,减少高并发访问带来的系统性能问题。
 一句话概括:经常查询一些不经常发生变化的数据,使用缓存来提高查询效率。
 像大多数的持久化框架一样,Mybatis也提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能。 Mybatis中缓存分为一级缓存,二级缓存。

  1. mybatis的缓存,都不需要我们手动存储和获取数据。mybatis自动维护的。
  2. mybatis开启了二级缓存后,那么查询顺序:二级缓存--》一级缓存--》数据库
  3. 注意:mybatis的二级缓存会存在脏读问题,需要使用第三方的缓存技术解决问题。

一级缓存

原理示意图

 一级缓存是SqlSession级别的缓存,是默认开启的。
 所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
一级缓存示意图

生命周期

 一级缓存是SqlSession范围的缓存,执行SqlSession的C(增加)U(更新)D(删除)操作,或者调用clearCache()、commit()、close()方法,都会清空缓存。
生命周期

1.第一次发起查询用户id为41的用户信息,先去找缓存中是否有id为41的用户信息,如果没有,从数据库查询用户信息。
2.得到用户信息,将用户信息存储到一级缓存中。
3.如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
4.第二次发起查询用户id为41的用户信息,先去找缓存中是否有id为41的用户信息,缓存中有,直接从缓存中获取用户信息。

清除缓存

在映射文件中的select标签中使用flushCache属性

<!-- 根据id查询用户:每次查询时,都会清除缓存 -->
    <select id="findById" resultType="com.soulboy.domain.User" parameterType="int" flushCache="true">
        select * from user where id = #{id}
    </select>

手动清除一级缓存

/**
     * 验证mybatis中的一级缓存
     */
    @Test
    public void testLevelOneCache() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //当前返回  其实是基于UserMapper所产生的代理对象(底层JDK动态代理:proxy)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //根据id查询用户信息第,第一次查询是数据库
        User user = userMapper.findById(1);
        System.out.println(user);

        //清除一级缓存
        sqlSession.clearCache();
        
        //第二次查询通过观察log4j控制台输出,并没有发现有: Preparing: select * from user where id = ?
        user = userMapper.findById(1);
        System.out.println(user);
        sqlSession.close();
    }

二级缓存

 二级缓存是namspace级别(跨sqlSession)的缓存,是默认不开启的
 二级缓存的开启需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置就可以开启二级缓存了。
二级缓存

验证流程
配置核心配置文件 sqlMapConfig.xml

<settings>
        <!--开启全局延迟加载功能-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--所有方法都会延迟加载-->
        <setting name="lazyLoadTriggerMethods" value="toString()"/>
        <!-- 因为cacheEnabled的取值默认就为true,
        所以这一步可以省略不配置。为true代表开启二级缓存;
        为false代表不开启二级缓存。 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

修改User实现Serializable接口

public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
}

配置映射文件UerMapper.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="com.soulboy.mapper.UserMapper">
    <!--当前映射文件开启二级缓存-->
    <cache></cache>
    <!-- 根据id查询用户,每次查询时,都会清除缓存-->
    <select id="findById" parameterType="int" resultType="com.soulboy.domain.User" useCache="true">
        select * from user where id = #{id}
    </select>
</mapper>

测试类

/**
     * 验证mybatis中的一级缓存
     */
    @Test
    public void testTwoCache() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        //SqlSession1
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = userMapper1.findById(1);
        System.out.println(user1);
        //只有执行sqlSession.commit或者sqlSession.close方法,一级缓存中的数据才会被刷新到二级缓存中
        sqlSession1.close();
        
        //SqlSession2
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = userMapper2.findById(1);    //12:00:39,294 DEBUG UserMapper:60 - Cache Hit Ratio [com.soulboy.mapper.UserMapper]: 0.5

        System.out.println(user2);
        sqlSession2.close();
    }

二级缓存会引发脏读

实际开发中不会使用二级缓存,mybatis的二级缓存因为是namespace级别,所以在进行多表查询时会产生脏读问题


作者:Soulboy