[阶段3 企业开发基础] 4. Mybatis

文章目录

1. 概述

1.1 简介

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

数据持久化

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

  • 内存:断电即失

Mybatis 特性

  • MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
  • MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
  • MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

与其他持久化技术比较

  • JDBC
    • SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA
    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动生产的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
    • 反射操作太多,导致数据库性能下降
  • Mybatis
    • 轻量级,性能出色
    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据

1.2 项目构建

1.2.1 导入mybatis依赖

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.7</version>
</dependency>

1.2.2 导入MySQL依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.37</version>
</dependency>

1.2.3 导入JUnit依赖

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

1.2.4 编写mybatis配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置-->
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
   
</configuration>

1.2.5 编写mybatis工具类

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    
    static {
        try {
            //获取SqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //执行SQL的对象
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

1.2.6 编写测试类

public interface UserDao {
    List<User> getUserList();
}
<?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.cyan.dao.UserDao">
    
  <select id="getUserList" resultType="com.cyan.pojo.User">
      SELECT * FROM `user`
  </select>
    
</mapper>
@Test
public void testGetUserList() {
    //获取SqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    //执行SQL 方式1 getMapper
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    List<User> userList = userDao.getUserList();

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

    sqlSession.close();
}

1.2.7 防止资源导出失败

<!--在build中配置resources,防止资源导出失败问题-->
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

1.3 日志

1.3.1 日志工厂

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

1.3.2 log4j

导入log4j依赖

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

log4j.properties

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

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

# 文件输出的相关设置
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./logs/server.log
log4j.appender.file.MaxFileSize=10000KB
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{MM-dd HH:mm:ss.SSS}[%24F:%-3L:%-5p]%x %m%n

# 日志 输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG

log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
	<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
		<param name="Encoding" value="UTF-8" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}%m (%F:%L) \n" />
		</layout>
	</appender>
	<logger name="java.sql">
		<level value="debug" />
	</logger>
	<logger name="org.apache.ibatis">
    	<level value="info" />
	</logger>
	<root>
		<level value="debug" />
		<appender-ref ref="STDOUT" />
	</root>
</log4j:configuration>

配置log4j为日志实现

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

2. 相关概念

2.1 Mapper接口

  • Mapper接口的取名应该是和映射文件名保持一致

  • 比如,某个实体类User,它的Mapper接口如下:

    public interface UserMapper{
        int insert();
    }
    
  • 在Mapper接口中可以提供一些抽象方法,用于增删改查

2.2 ORM思想

  • ORM是指(Object Relationship Mapping)对象关系映射

  • 其中

    • 对象:Java的实体类对象
    • 关系:关系型数据库
    • 映射:二者之间的对应关系
Java概念数据库概念
属性字段/列
对象记录/行

3. 映射配置文件

3.1 文件结构

  • 命名规则:数据库表对应的类名+Mapper.xml

  • 一个映射文件对应一个实体类,对应一个表中的操作

  • 映射文件主要用于编写SQL、访问以及操作表中的数据

  • 映射文件存放位置是maven工程下的src/main/resources/mappers目录下

  • 映射配置文件要保证两个一致

    • mapper接口的全类名和映射文件的命名空间namespace保持一致
    • mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

映射文件的结构

<?xml version="1.0" encoding="UTF-8" ?>
<!--DTD约束-->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="Mapper接口全类名">
    <!--映射语句-->
    <insert id="Mapper接口方法名">
        SQL语句
    </insert>

</mapper>

3.2 映射配置文件标签

标签

  • 插入数据
  • id属性指定对应的mapper接口方法名
<insert id="insertUser">
    SQL语句
</insert>

delete 标签

  • 删除数据
  • id属性指定对应mapper接口的方法名
<delete id="deleteUser">
    SQL语句
</delete>

update 标签

  • 更新数据

  • id属性指定对应mapper接口中的方法名

<update id="updateUser">
    SQL语句
</update>

select 标签

  • 查询数据

  • id属性指定mapper接口中对应的方法名

  • resultType属性表示自动映射,用于属性名和表中字段名一致的情况

  • resultMap属性表示自定义映射,用于一对多或多对一或字段名和属性名不一致的情况

查询一条数据

<select id="getUserById" resultType="com.lxq.pojo.User">
    SQL语句
</select>

查询多条数据

<select id="getUserList" resultType="com.lxq.pojo.User">
    SQL语句
</select>

3.3 SQL语句中参数获取

3.3.1 获取方式

  • MyBatis获取参数值的两种方式:${}和#{}

  • ${}的本质是字符串拼接,#{}的本质是占位符赋值

  • ${}方式会将参数原原本本拼接入SQL语句中,如果参数是字符串类型的需要手动加上引号

`${参数}`
  • #{}的方式会自动加上引号,如果是字符串类型的参数无需手动加上引号
#{参数}

注意:两种方式区别

3.3.2 参数类型

单个字面量类型的参数

  • 字面量是指基本数据类型以及包装类还有自定义类等
  • 获取方式
<select id="getUserByUsername" resultType="User">
    select * from t_user where username=#{username}
</select>
  • 此时获取参数时花括号里边的参数名不做要求,可以是随意的名称,如果使用${}的方式记得加引号

多个字面量类型的参数

  • 若mapper接口中的方法参数为多个时,MyBatis会自动将这些参数放在一个map集合中,此时参数名不能是随意的名称
  • 这个map集合存放参数的形式是arg0,arg1...为map中的键,参数值为map中的值;还有param1,param2...为map中的键,参数值为map值的值
  • arg和param是同时存在于同一个map中的,在SQL语句获取参数时只需指定这些键的名称即可
  • 获取方式
<!--arg的方式-->
<select id="checkLogin" resultType="User">
    select * from t_user where username = #{arg0} and password = #{arg1}
</select>
<!--param的方式-->
<select id="checkLogin" resultType="User">
        select * from t_user where username = #{param1} and password = #{param2}
</select>

  • 如果使用${}的方式记得加引号

map集合类型的参数

  • 为了在SQL语句中指定一些有意义的参数名,我们可以自己提供一个map集合,自定义一些键的名称即可
  • 通过自定义键的名称,我们在SQL语句里就可以使用自定义的参数名了
  • 比如,这个自定义的map集合可以是{("username","参数值"),("password","参数值")}
  • 获取方式
<select id="checkLoginByMap" resultType="User">
    select * from t_user where username = #{username} and password = #{password}
</select>

  • 如果使用${}的方式记得加引号

使用@Param注解标识的参数

如果每次使用多个参数时都要自定义map集合就太麻烦了,所以可以通过使用@Param注解在映射方法的形参中指定好参数名

  • 映射方法
User getUserByParam(@Param("username") String username,@Param("password") String password);

  • 获取方式
<select id="checkLoginByParam" resultType="User">
    select * from t_user where username = #{username} and password = #{password}
</select>

  • 使用这种注解就可以方便获取参数了,如果使用${}的方式记得加引号

实体类型的参数

  • 如果映射方法的形参是一个实体类型时,可以通过访问实体类对象中的属性名获取属性值
  • 实体类
public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private String gender;
    private String email;
    // 省略有参、无参构造方法以及toString()方法
   // set and get method
}

  • 获取方式
<insert id="insertUser">
    insert into t_user value(null,#{username},#{password},#{age},#{gender},#{email})
</insert>

  • 如果使用${}的方式记得加引号

总结

  • 上面所说的五种参数类型实际上可以分成两种类型,一种是使用**@Param注解一种是使用实体类**
  • 就是说不论单个参数或者多个参数,都用注解的方式,如果是实体类那就用实体类属性的方式

3.4 SQL操作

3.4.1 查询单个实体对象

映射方法:

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

映射文件:

<select id="getUserById" resultType="User">
    select * from t_user where id = #{id}
</select>

3.4.2 查询单个List集合

映射方法:

List<User> getAllUser();

映射文件:

<select id="getAllUser" resultType="User">
    select * from t_user
</select>

注意:当查询的数据为多条时,不能使用实体类作为返回值,否则会抛出异常 TooManyResultsException;但是若查询的数据只有一条,可以使用实体类或集合作为返回值

3.4.3 查询单个数据

映射方法:

int getCount();

映射文件:

<select id="getCount" resultType="java.lang.Integer">
    select count(id) from t_user
</select>

3.4.4 查询单条数据到map集合

映射方法:

Map<String,Object> getUserToMap(@Param("id") int id);

映射文件:

<select id="getUserToMap" resultType="java.util.Map">
    select * from t_user where id = #{id}
</select>

注意:将一条数据查询到map集合中时,map的键是表中的字段名,map的值是表中数据

3.4.5 查询多条数据到map集合

  • 方式1

映射方法:

List<Map<String,Object>> getAllUserToMap();

映射文件:

<select id="getAllUserToMap" resultType="java.util.Map">
    select * from t_user
</select>

  • 方式2

映射方法:

@MapKey("id")
Map<String,Object> getAllUserToMap();

映射文件:

<select id="getAllUserToMap" resultType="java.util.Map">
    select * from t_user
</select>

注意

  • 方式一中每条查出来的数据都对应一个Map集合,然后再利用List集合将这些Map集合组织起来

  • 方式二中每条查出来的数据都存放在一个Map集合中,但是这个Map集合的键由映射方法上方的@MapKey注解指定,而Map集合的值又是另外一个Map集合,作为值的Map集合中键对应表中字段名,值对应表中数据

// way2
{
1={password=123456, sex=男, id=1, age=23, username=admin},
2={password=123456, sex=男, id=2, age=23, username=张三},
3={password=123456, sex=男, id=3, age=23, username=张三}
}

3.4.6 模糊查询

映射方法:

List<User> getUserByLike(@Param("mohu") String mohu);

映射方法:

<select id="getUserByLike" resultType="User">
    <!--方式1-->
    select * from t_user where username like '%${mohu}%'
    <!--方式2-->
    select * from t_user where username like concat("%",#{mohu},"%")
    <!--方式3-->
    select * from t_user where username like "%"#{mohu}"%"
</select>

注意:不能使用like '%#{mohu}%'的方式,因为#{}会被解析成?,这个问号会被当成字符串的一部分造成参数获取失败

3.4.7 批量删除

映射方法:

void deleteSomeUser(@Param("ids") String ids);

映射文件:

<delete id="deleteSomeUser">
    delete from t_user where id in(${ids})
</delete>

注意:这里获取参数的方式是${},因为#{}会自动添加引号,如果使用#{}的方式会造成SQL语句解析成delete from t_user where id in('ids')从而报错

3.4.8 动态设置表名

映射方法:

List<User> getUserList(@Param("table") String table);

映射文件:

<select id="getUserList" resultType="User">
    select * from ${table}
</select>

注意:这里使用${}是因为使用#{}时会自动添加引号,而表名不允许添加表名

3.4.9 执行添加功能时获取自增的主键

映射方法:

void insertUser(User user);

映射文件:

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})
</insert>

测试方法:

@Test
public void testInsertUser(){
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    SpecialSQLMapper mapper = sqlSession.getMapper(SpecialSQLMapper.class);
    User user = new User(null,"李晨","1234567",46,"男","lichen@qq.com");
    mapper.insertUser(user);
    System.out.println(user);//在这一步中打印出的User对象中可以看到自增的id,如果配置文件中不使用useGeneratedKeys和keyProperty,则id仍然是null
}

注意:这里的useGeneratedKeys设置使用自增主键为true,keyProperty是将获取的主键值赋给实体对象中的某个属性。这样,在添加这个实体对象后,自增的主键也能在实体对象中获得,而不需要进行查询

3.4.10 增删改查案例

int addUser(User user);

<insert id="addUser" parameterType="com.cyan.pojo.User">
  INSERT INTO `user` (`id`, `name`, `pwd`) VALUES (#{id}, #{name}, #{pwd});
</insert>

//增删改需要提交事务
@Test
public void testAddUser() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserDao userDao = sqlSession.getMapper(UserDao.class);

    int res = userDao.addUser(new User(4, "哈七","123456"));
    if (res > 0) {
        System.out.println("Add Success");
    }

    //提交事务
    sqlSession.commit();
    sqlSession.close();

}

int deleteUser(int id);

<delete id="deleteUser" parameterType="int">
  DELETE FROM `user` WHERE `id` = #{id};
</delete>

@Test
public void testDeleteUser() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserDao userDao = sqlSession.getMapper(UserDao.class);

    userDao.deleteUser(4);

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

int updateUser(User user);

<update id="updateUser" parameterType="com.cyan.pojo.User">
  UPDATE `user` SET `name` = #{name}, `pwd` = #{pwd} WHERE `id` = #{id};
</update>

@Test
public void testUpdateUser() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserDao userDao = sqlSession.getMapper(UserDao.class);

    userDao.updateUser(new User(4,"小羊", "147257"));

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

List<User> getUserList();
User getUserById(int id);

<select id="getUserList" resultType="com.cyan.pojo.User">
  SELECT * FROM `user`
</select>

<select id="getUserById" parameterType="int" resultType="com.cyan.pojo.User">
  SELECT * FROM `user` WHERE `id` = #{id}
</select>

@Test
public void testGetUserList() {
    //获取SqlSession对象
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    //执行SQL
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    List<User> userList = userDao.getUserList();

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

    sqlSession.close();
}

@Test
public void testGetUserById() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserDao userDao = sqlSession.getMapper(UserDao.class);

    User user = userDao.getUserById(1);
    System.out.println(user);

    sqlSession.close();
}

3.5 处理表字段和实体类属性名不一致的情况

3.5.1 方式一 给字段名取别名

  • 如果表中字段名和实体类属性名不一致,可以在SQL语句中给字段名取别名
  • 给字段取得别名必须和实体类属性名一致

3.5.2 方式二 在核心配置文件中配置驼峰映射

  • 使用前提:表字段符合Mysql命名规范(使用下划线_分割单词),而实体类属性符合驼峰命名规范

  • 使用方式:

    1. 在核心配置文件中使用**<settings>标签,在该标签下使用<setting>**子标签来配置
    2. 给子标签**<setting>设置name属性值为mapUnderscoreToCamelCase**,value属性值为true
  • 范例

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true" />
</settings>

  • 在核心配置文件使用了如上配置后,在SQL语句中可以使用表的字段名而不用考虑表字段名和实体类属性名不一致的情况

3.5.3 方式三 在映射配置文件中使用<resultMap>标签自定义映射

  • <resultMap>标签含有id属性和type属性,其中id属性是设置当前自定义映射的标识,type属性是映射的实体类
  • <resultMap>标签下含有的子标签以及功能
    • <id>标签:设置主键字段的映射关系,使用column属性设置映射关系中表的字段名,使用property属性设置映射关系中实体类的属性名
    • <result>标签:设置普通字段的映射关系,使用column属性设置映射关系中表的字段名,使用property属性设置映射关系中实体类的属性名
<resultMap id="empResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
</resultMap>

<select id="getEmpByEmpId" resultType="Emp" resultMap="empResultMap">
    select * from t_emp where emp_id = #{empId}
</select>

注意:SQL语句所在标签中的resultMap属性值必须是自定义映射的id

3.6 多对一映射关系的处理

这里多对一是指实体类中某个属性是以表中多个字段为属性构成的实体类,如员工类的部门属性,部门属性的类型是部门类,这个部门类有部门id,部门名称

3.6.1 方式一 使用级联

<resultMap>配置

<resultMap id="getEmpAndDeptByEmpIdResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
    <result column="dept_id" property="dept.deptId"></result>
    <result column="dept_name" property="dept.deptName"></result>
</resultMap>

<select id="getEmpAndDeptByEmpId" resultMap="getEmpAndDeptByEmpIdResultMap">
      select emp_id,emp_name,age,gender,t_dept.dept_id,dept_name
      from t_emp left join t_dept
      on t_emp.dept_id = t_dept.dept_id where emp_id = #{empId}
</select>

3.6.2 方式二 使用<association>标签

<resultMap>配置

<resultMap id="getEmpAndDeptByEmpIdResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
    <association property="dept" javaType="Dept">
        <id column="dept_id" property="deptId"></id>
        <result column="dept_name" property="deptName"></result>
    </association>
</resultMap>

<select id="getEmpAndDeptByEmpId" resultMap="getEmpAndDeptByEmpIdResultMap">
      select emp_id,emp_name,age,gender,t_dept.dept_id,dept_name
    from t_emp left join t_dept
      on t_emp.dept_id = t_dept.dept_id where emp_id = #{empId}
</select>

注意:
association标签中property属性是指映射实体类中属性的名称,javaType是实体类的类型,
而association标签下的id标签和result标签中的property属性是指javaType指定的类中的属性名称,column属性指表中的字段名

3.6.3 方式三 使用分步查询

<resultMap>配置

查询员工信息:

<resultMap id="getEmpAndDeptByEmpIdResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
    <association 
                 property="dept"
                 select="com.liaoxiangqian.mapper.DeptMapper.getDeptByDeptId"
                 column="dept_id">
    </association>
</resultMap>

<select id="getEmpAndDeptByEmpId" resultMap="getEmpAndDeptByEmpIdResultMap">
    select * from t_emp where emp_id = #{empId}
</select>

根据员工的部门id查询部门信息:

<resultMap id="getDeptByDeptIdResultMap" type="Dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>
</resultMap>

<select id="getDeptByDeptId" resultMap="getDeptByDeptIdResultMap">
    select * from t_dept where dept_id = #{deptId}
</select>

3.6.3 案例分析

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师'); 

CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

按照查询嵌套处理

public interface StudentMapper {

    //查询学生的信息以及对应的老师的信息
    List<Student> getStudent();
}

<select id="getStudent" resultMap="StudentTeacher">
    SELECT * FROM `student`
</select>
<resultMap id="StudentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--复杂属性 单独处理 
        对象:association
        集合:collection
    -->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="Teacher">
    SELECT * FROM `teacher` WHERE `id` = #{id};
</select>

按照结果嵌套处理

<!--按照结果嵌套处理-->
<select id="getStudent2" resultMap="StudentTeacher2">
    SELECT s.`id` sid, s.`name` sname, t.`name` tname
    FROM `student` s, `teacher` t
    WHERE s.tid = t.id
</select>

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

3.7 一对多映射关系的处理

这里一对多是指实体类中某个属性是由许多实体类构成的集合,如部门类中员工属性是一个List集合

3.7.1 方式一 使用标签

配置

<resultMap id="getDeptAndEmpByDeptIdResultMap" type="Dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>
    <collection property="emps" ofType="Emp">
        <id column="emp_id" property="empId"></id>
        <result column="emp_name" property="empName"></result>
        <result column="age" property="age"></result>
        <result column="gender" property="gender"></result>
    </collection>
</resultMap>

<select id="getDeptAndEmpByDeptId" resultMap="getDeptAndEmpByDeptIdResultMap">
    select *
    from t_dept left join t_emp
    on t_dept.dept_id = t_emp.dept_id
    where t_dept.dept_id = #{deptId}
</select>

3.7.2 方式二 使用分步查询

<resultMap>配置

查询部门信息:

<resultMap id="getDeptAndEmpByDeptIdResultMap" type="Dept">
    <id column="dept_id" property="deptId"></id>
    <result column="dept_name" property="deptName"></result>
    <collection property="emps" 
                select="com.liaoxiangqian.mapper.EmpMapper.getEmpByDeptId" 
                column="dept_id">
    </collection>
</resultMap>
    
<select id="getDeptAndEmpByDeptId" resultMap="getDeptAndEmpByDeptIdResultMap">
    select * from t_dept where dept_id = #{deptId}
</select>

根据部门id查询员工信息:

<resultMap id="getEmpByDeptIdResultMap" type="Emp">
    <id column="emp_id" property="empId"></id>
    <result column="emp_name" property="empName"></result>
    <result column="age" property="age"></result>
    <result column="gender" property="gender"></result>
</resultMap>

<select id="getEmpByDeptId" resultMap="getEmpByDeptIdResultMap">
    select * from t_emp where dept_id = #{deptId}
</select>

3.7.3 案例分析

public class Student {
    private int id;
    private String name;

    private int tid;
}
public class Teacher {
    private int id;
    private String name;

    private List<Student> students;
}

按照查询结果嵌套处理

<select id="getTeacher" resultMap="TeacherStudentMap">
    SELECT s.id `sid`, s.name `sname`, t.name `tname`, t.id `tid`
    FROM student s, teacher t
    WHERE s.tid = t.id AND t.id = #{tid}
</select>

<resultMap id="TeacherStudentMap" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

按照查询嵌套处理

<select id="getTeacher2" resultMap="TeacherStudentMap2">
    SELECT * FROM teacher WHERE id = #{tid}
</select>
<resultMap id="TeacherStudentMap2" type="Teacher">
    <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
    SELECT * FROM `student` WHERE tid = #{tid}
</select>

3.8 分步查询的优点

  • 分步查询的优点是可以实现延迟加载

  • 延迟加载可以避免在分步查询中执行所有的SQL语句,节省资源,实现按需加载

  • 需要在核心配置文件中添加如下的配置信息

<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

  • lazyLoadingEnabled表示全局的延迟加载开关,true表示所有关联对象都会延迟加载,false表示关闭

  • aggressiveLazyLoading表示是否加载该对象的所有属性,如果开启则任何方法的调用会加载这个对象的所有属性,如果关闭则是按需加载

  • 由于这个配置是在核心配置文件中设定的,所以所有的分步查询都会实现延迟加载,而如果某个查询不需要延迟加载,可以在collection标签或者association标签中的fetchType属性设置是否使用延迟加载,属性值lazy表示延迟加载,属性值eager表示立即加载

3.9 动态 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;

3.9.1 if 标签

if标签通过test属性给出判断的条件,如果条件成立,则将执行标签内的SQL语句

<select id="queryBlogIf" parameterType="map" resultType="blog">
    SELECT * FROM blog 
    <where>
        <if test="title != null">
            AND title = #{title}
        </if>
        <if test="author != null">
            AND author = #{author}
        </if>
    </where>
</select>

3.9.2 where 标签

考虑if标签中的范例出现的一种情况:当第一个if标签条件不成立而第二个条件成立时,拼接成的SQL语句中where后面连着的是and,会造成SQL语句语法错误,而where标签可以解决这个问题

<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp
    <where>
        <if test="empName != null and empName != ''">
            emp_name = #{empName}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
        <if test="gender != null and gender != ''">
            and gender = #{gender}
        </if>
    </where>
</select>

where标签只会在子标签返回任何内容的情况下才插入WHERE子句。而且,若子句的开头有多余的and或者or,where标签也会将它们去除,但是子句末尾的and或者or不能去除

3.9.3 trim 标签

  • trim标签用于去掉或添加标签中的内容

  • trim标签常用属性

  1. prefix:在trim标签中的内容的前面添加某些内容
  2. prefixOverrides:在trim标签中的内容的前面去掉某些内容
  3. suffix:在trim标签中的内容的后面添加某些内容
  4. suffixOverrides:在trim标签中的内容的后面去掉某些内容
  • 用trim实现where标签范例相同的功能
<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp
    <trim prefix="where" prefixOverrides="and">
        <if test="empName != null and empName != ''">
            emp_name = #{empName}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
        <if test="gender != null and gender != ''">
            and gender = #{gender}
        </if>
    </trim>
</select>

3.9.4 choose、when、otherwise 标签

这三个标签是组合使用的,用于在多条件中选择一个条件,类似Java中的if…else if…else…语句

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    SELECT * FROM blog
    <where>
        <choose>
            <when test="title != null">
                title = #{title}
            </when>
            <when test="author != null">
                author = #{author}
            </when>
            <when test="views != null">
                views = #{views}
            </when>
        </choose>
    </where>
</select>

当某个when标签的条件满足时将对应的SQL语句返回,如果都不满足并且有otherwise标签时,才会返回otherwise标签中的SQL语句

3.9.5 foreach 标签

  • foreach标签允许指定一个集合或数组,并且对这个集合或数组进行遍历

  • foreach标签可以用的属性有

    1. collection:指定需要遍历的集合或数组
    2. item:当前遍历到的元素
    3. index:当前遍历到的元素的序号
    4. 当遍历的集合是Map类型时,index表示键,item表示值
    5. open:指定遍历开始前添加的字符串
    6. close:指定遍历开始后添加的字符串
    7. separator:指定每次遍历之间的分隔符
  • collection属性值注意事项

    • 如果遍历的是List时,属性值为list
    • 如果遍历的是数组时,属性值为array
    • 如果遍历的是Map时,属性值可以是map.keys()map.values()map.entrySet()
    • 除此之外,还可以在映射方法的参数中使用**@Param()**注解自定义collection属性值
  • 批量添加数据

<insert id="addMoreEmp">
    insert into t_emp values
    <foreach collection="list" separator="," item="emp">
        (null,#{emp.empName},#{emp.age},#{emp.gender},null)
    </foreach>
</insert>

  • 批量删除数据
<delete id="deleteMoreEmp">
    delete from t_emp where emp_id in
    <foreach collection="array" item="empId" separator="," open="(" close=")">
        #{empId}
    </foreach>
</delete>

[补充案例]

<!--SELECT * FROM blog WHERE 1=1 AND (id=1 OR id = 2 OR id = 3)-->
<select id="queryForeach" parameterType="map" resultType="blog">
    SELECT * FROM blog
    <where>
        <foreach collection="ids" item="id" open="AND (" close=")" separator="OR">
            id = #{id}
        </foreach>
    </where>
</select>

3.9.6 sql 标签

  • 基于单表定义SQL片段

  • 不能存在where标签

  • 用于记录一段通用的SQL语句片段,在需要用到该SQL语句片段的地方中通过include标签将该SQL语句片段插入

  • sql标签通过id属性唯一标识一个SQL语句片段,include标签通过refid属性指定使用某个SQL片段

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

<select id="queryBlogIf" parameterType="map" resultType="blog">
    SELECT * FROM blog
    <where>
        <include refid="if-title-author"/>
    </where>
</select>

3.9.7 set 标签

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

4. 核心配置文件

4.1 文件结构

  • 核心配置文件命名建议是mybatis-config.xml

  • 核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息

  • 核心配置文件存放的位置是maven工程下的src/main/resources目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!--DTD约束-->
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--
        1.environments配置数据库的环境,环境可以有多个
        2.default属性指定使用的环境
    -->
    <environments default="development">
        <!--
            1.environment配置具体某个数据库的环境
            2.id属性唯一标识这个环境
        -->
        <environment id="development">
            <!--
                1.transactionManager设置事务管理方式
                2.type属性取值有“JDBC|MANAGED”
                3.JDBC指当前环境中使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理
                4.MANAGED指被管理,例如Spring中
            -->
            <transactionManager type="JDBC"/>
            <!--
                1.dataSource配置数据源
                2.取值有"POOLED|UNPOOLED|JNDI"
                3.POOLED表示使用数据库连接池缓存数据库连接
                4.UNPOOLED:表示不使用数据库连接池
                5.JNDI表示使用上下文中的数据源
            -->
            <dataSource type="POOLED">
                <!--设置链接数据库的驱动-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--设置连接数据库的地址-->
                <property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
                <!--设置连接数据库的用户名-->
                <property name="username" value="root"/>
                <!--设置连接数据库的密码-->
                <property name="password" value="lxq"/>
            </dataSource>
        </environment>
    </environments>
    <!--mappers用于引入映射的配置文件-->
    <mappers>
        <!--mapper用于指定某个映射文件,resource属性指定文件路径-->
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

4.2 核心配置文件详解

4.2.1 标签顺序

  • 核心配置文件中configuration标签下的子标签要按照一定的顺序书写
  • properties => settings => typeAliases => typeHandlers => objectFactory => objectWrapperFactory => reflectorFactory => plugins => environments => databaseIdProvider => mappers

4.2.2 标签详解

configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

<properties>标签

  • 用于引入某个properties配置文件,是一个单标签

  • resource属性指定配置文件

  • 范例

    <properties resource="jdbc.properties" />
    
    

<typeAliases>标签

  • 用于为某个类的全类名设置别名,子标签是<typeAlias>

  • 一个子标签对应设置一个类的别名

  • 子标签下有type和alias两个属性,type指定需要设置别名的类的全类名,alias指定别名

  • 如果只设置了type属性,那么默认的别名就是它的类名(不是全类名)而且不区分大小写

  • 如果想要设置某个包下所有类的别名,可以使用<package>标签,用name属性指定包名

  • 范例

<typeAliases>
    <typeAlias type="com.cyan.pojo.User" alias="User"/>
</typeAliases>

扫描实体类的包,它的默认名为该类的类名,首字母小写

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

  • MyBatis中内建了一些类型的别名,常见的有
Java类型别名
int_int或_integer
Integerint或integer
Stringstring
Listlist
Mapmap

<property>标签

  • 用于配置连接数据库时用到的各种属性,是一个单标签

  • 该标签有两个属性,一个是name指定属性名,另一个是value指定属性值

  • 如果不使用<properties>标签引入相关配置文件时,使用方式如下

    <property name="driver" value="com.mysql.jdbc.Driver" />
    
    
  • 如果使用<properties>标签引入相关的配置文件时,value属性可以写成如下形式

    <property name="driver" value="${jdbc.driver}" />
    
    

    其中配置文件的内容是

    jdbc.driver=com.mysql.jdbc.Driver
    
    

    注意:这里使用jdbc.driver来给键命名是因为核心配置文件中可能会引入其他的配置文件,如果使用driver来命名键的话有可能会跟其他配置文件中的键同名而产生冲突

<mappers>标签

  • 该标签用于引入映射文件

  • 每个映射文件使用子标签<mapper>来表示,该子标签是一个单标签

  • 子标签<mapper>使用属性resource来指定需要引入的映射文件

  • 如果想要将某个包下所有的映射文件都引入,可以使用<package>标签,使用name属性来指定需要引入的包

  • 范例

    <mappers>
        <mapper resource="mappers/UserMapper.xml" />
        <package name="com.lxq.mapper" />
    </mappers>
    
    

    注意:使用包的形式引入映射文件需要满足两个条件,1.mapper接口所在的包和映射文件所在的包要一致;2.mapper接口名和映射文件名要相同

4.3 环境配置

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <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>

  • 默认使用的环境 ID(比如:default=“development”)。
  • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
  • 事务管理器的配置(比如:type=“JDBC”)。
  • 数据源的配置(比如:type=“POOLED”)。

4.4 属性

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

<!--引入外部配置文件-->
<properties resource="db.properties"/>

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

  • 可以直接引入外部文件
  • 可以添加属性配置
  • 外部配置文件优先级高于内部字段

5. 相关API

5.1 Resources

  • Resources类由MyBatis提供用于获取来自核心配置文件的输入流
  • 相关方法是:InputStream getResourceAsStream(String filepath),注意这是一个静态方法

5.2 SqlSessionFactoryBuilder

  • SqlSessionFactoryBuilder类由MyBatis提供用于获取SqlSessionFactory的实例对象
  • 相关方法是:SqlSessionFactory build(InputStream is),该方法通过一个输入流返回了SqlSessionFactory对象

5.3 SqlSessionFactory

  • SqlSessionFactory类由MyBatis提供用于获取SqlSession对象,每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为核心的
  • 相关方法:SqlSession openSession()SqlSession openSession(boolean autoCommit),这两个方法都用于获取SqlSession对象,如果使用有参数的可以指定是否自动提交事务,没有指定参数的默认是不自动提交事务

5.4 SqlSession

  • SqlSession类由MyBatis提供用于执行SQL、管理事务、接口代理

  • 常用方法

    方法说明
    void commit()提交事务
    void rollback()回滚事务
    T getMapper(Class<T> aClass)获取指定接口的代理实现类
    void close()释放资源
  • 除了以上常用方法外,SqlSession还有很多有关数据库增删改查的方法,但是这些方法繁琐而且不符合类型安全,所以使用getMapper()方法来获取一个Mapper接口的代理实现类来执行映射语句是个比较方便的做法

5.5 实践

  • SqlSessionFactoryBuilder

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

  • SqlSessionFactory

    SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此  SqlSessionFactory 的最佳范围是应用范围。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

  • SqlSession

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

  • 工具类

public class SqlSessionUtil {

    private static SqlSessionFactory sqlSessionFactory;

    static{
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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


6.缓存

1.什么是缓存

  • 存在内存中的临时数据
  • 将用户经常查询的数据放在缓存中,用户去查询数据就不用从词频上查询,从缓存中查询,提高查询效率,解决了高并发系统性能问题

2.为什么使用缓存

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

3.什么样的数据能使用缓存

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

6.1 一级缓存

  • 一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问,一级缓存是默认开启的
  • 一级缓存失效的四种情况:
    • 使用另一个SqlSession
    • 同一个SqlSession但是查询条件不同
    • 同一个SqlSession但是两次查询中间执行了任何一次增删改操作
    • 同一个SqlSession但是两次查询中间手动清空了缓存,手动清空缓存的方法是调用SqlSession的clearCache()方法

6.2 二级缓存

  • 二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存,此后若再次执行相同的查询语句,结果就会从缓存中获取
  • 二级缓存开启的条件:
    • 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
    • 在映射文件中设置标签<cache/>
    • 二级缓存必须在SqlSession关闭或提交之后有效
    • 查询的数据所转换的实体类类型必须实现序列化的接口
  • 二级缓存失效的情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
  • <cache/>可以设置的一些属性:
    • eviction属性:缓存回收策略,默认的是 LRU
      1. LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象
      2. FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们
      3. SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象
      4. WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
    • flushInterval属性:刷新间隔,单位是毫秒,默认情况下不设置也就是没有刷新间隔,缓存仅仅调用语句时刷新
    • size属性:引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出
    • readOnly属性:只读, 取值是true/false
      1. true:只读缓存,会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势
      2. false:读写缓存,会返回缓存对象的拷贝(通过序列化),这会慢一些,但是安全,因此默认是false

6.3 缓存顺序

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
  • 如果二级缓存没有命中,再查询一级缓存
  • 如果一级缓存也没有命中,则查询数据库
  • 注意SqlSession关闭之后,一级缓存中的数据才会写入二级缓存

6.4 整合第三方的缓存 EHCache

6.4.1 添加依赖

<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>

6.4.2 jar包功能

jar包名作用
mybatis-ehcacheMybatis和EHCache的整合包
ehcacheEHCache核心包
ehcacheSLF4J日志门面包
logback-classic支持SLF4J门面接口的一个具体实现

6.4.3 创建EHCache的配置文件ehcache.xml

<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\atguigu\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>

6.4.4 设置二级缓存类型

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

6.4.5 加入logback日志

存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。

创建logback的配置文件logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -
->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
[%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>

6.4.6 EHCache配置文件说明

属性名是否必须作用
maxElementsInMemory在内存中缓存的element的最大数目
maxElementsOnDisk在磁盘上缓存的element的最大数目,若是0表示无
穷大
eternal设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根timeToIdleSeconds、timeToLiveSeconds判断
overflowToDisk设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
timeToIdleSeconds当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMBDiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskPersistent在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskExpiryThreadIntervalSeconds磁盘缓存的清理线程运行间隔,默认是120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

7. 分页

7.1 limit 分页

SELECT * FROM user LIMIT startIndex,pageSize;

7.2 mybatis 分页

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

<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
  SELECT * FROM `user` LIMIT #{startIndex}, #{pageSize}
</select>

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

    HashMap<String, Integer> params = new HashMap<>();
    params.put("startIndex", 0);
    params.put("pageSize", 2);

    List<User> userList = mapper.getUserByLimit(params);
    for (User user : userList) {
        System.out.println(user);
    }
}

7.3 分页插件

  • 添加依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
  • 配置分页插件:在Mybatis核心文件中配置插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

7.4 使用分页插件

  1. 在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
  • pageNum:当前页的页码
  • pageSize:每页显示的条数
  1. 在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, int navigatePages)获取分页相关数据
  • list:分页之后的数据
  • navigatePages:导航分页的页码数
  1. 分页相关数据
PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,
pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
navigatepageNums=[4, 5, 6, 7, 8]
}

常用数据:

pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]

8. 注解开发和Lombok使用

8.1 注解开发

public interface UserMapper {
    @Select("SELECT * FROM user")
    List<User> getUsers();
}

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

[注意]

@Param()
基本类型的参数或者String类型需要加上
引用类型不需要加   
SQL中引用的是@Param("id")设定的属性名  

8.2 Lombok

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
    <scope>provided</scope>
</dependency>

@Data :无参构造, setter ,getter,toString, hashcode,equals

@Data
@AllArgsConstructor //有参构造
@NoArgsConstructor //无参构造

9. 逆向工程

  • 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
  • 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
    • Java实体类
    • Mapper接口
    • Mapper映射文件

9.1 创建逆向工程

9.1.1 添加依赖和插件

<!-- 依赖MyBatis核心包 -->
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

9.1.2 创建Mybatis核心配置文件

9.1.3 创建逆向工程的配置文件

文件名必须是:generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis"
userId="root"
password="123456">
</jdbcConnection>
<!-- javaBean的生成策略-->
<javaModelGenerator targetPackage="com.atguigu.mybatis.bean"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="t_emp" domainObjectName="Emp"/>
<table tableName="t_dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>

9.1.4 执行MBG插件的generate目标

在这里插入图片描述
效果:
在这里插入图片描述

9.2 QBC 查询

@Test
public void testMBG() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSession sqlSession = new
SqlSessionFactoryBuilder().build(is).openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
EmpExample empExample = new EmpExample();
//创建条件对象,通过andXXX方法为SQL添加查询添加,每个条件之间是and关系
empExample.createCriteria().andEnameLike("a").andAgeGreaterThan(20).andDidIsNot
Null();
//将之前添加的条件通过or拼接其他条件
empExample.or().andSexEqualTo("男");
List<Emp> list = mapper.selectByExample(empExample);
for (Emp emp : list) {
System.out.println(emp);
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyan Chau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值