MyBatis

本文详细介绍了MyBatis的核心依赖、配置文件、Java代码实现、核心操作、配置细节、缓存、进阶特性和PageHelper分页插件的使用。内容包括Mapper.xml配置、实体类、Dao接口、主键回填、一级缓存、二级缓存、动态SQL、日志整合、关联关系映射等关键知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MyBatis

一、核心依赖

在pom.xml文件中导入MyBatis的核心依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0"  
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation=
         "http://maven.apache.org/POM/4.0.0 
          http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>

    <!--项目配置-->
    <groupId>com.qf</groupId>
    <artifactId>hello-mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--依赖-->
    <dependencies>
        <!--MyBatis核心依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <!--MySql驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    </dependencies>
</project>

二、MyBatis核心配置文件

2.1 db.properties

数据库连接properties配置文件

#jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/example?useUnicode=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF8
jdbc.username=root
jdbc.password=root
2.2 mybatis-config.xml

mybatis-config.xml配置文件

注意:mybatis-config.xml默认建议存放在resources中,路径不能以/开头

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

<!--MyBatis配置-->
<configuration>
  <!--添加properties配置文件路径(外部配置、动态替换)-->
    <properties resource="jdbc.properties" />
    <!--JDBC环境配置、选中默认环境-->
    <environments default="MySqlDB">
        <!--MySql数据库环境配置-->
        <environment id="MySqlDB">
            <!--事务管理-->
            <transactionManager type="JDBC"/>
            <!--连接池-->
            <dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory">
                 <!--使用$ + 占位符-->
                <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>

    <!--Mapper注册-->
    <mappers>
        <!--注册Mapper文件的所在位置-->
        <mapper resource="xxxMapper.xml"/>
    </mappers>
</configuration>
2.3 Mapper.xml

XXXMapper.xml配置文件放在resources目录下的mapper文件夹中

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

<!--namespace = 所需实现的接口全限定名-->
<mapper namespace="com.qf.mybatis.part1.basic.UserDao">
    <!--id = 所需重写的接口抽象方法,resultType = 查询后所需返回的对象类型-->
    <select id="selectUserById" resultType="com.qf.mybatis.part1.basic.User">
        <!--#{arg0} = 方法的第一个形参-->
        SELECT * FROM t_users WHERE id = #{arg0}
    </select>
</mapper>

将Mapper.xml注册到mybatis-config.xml中

<!--Mapper文件注册位置-->
<mappers>
    <!--注册Mapper文件-->
    <mapper resource="UserDaoMapper.xml"/>
</mappers>

解决mapper.xml存放在resources以外路径中的读取问题:

在pom.xml文件最后追加< build >标签,以便可以将xml文件复制到classes中,并在程序运行时正确读取

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>*.xml</include><!-- 默认(新添加自定义则失效) -->
                <include>**/*.xml</include><!-- 新添加 */代表1级目录 **/代表多级目录 -->
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

三、Java代码

3.1 实体类

定义所需CRUD操作的实体类

package com.qf.mybatis.part1.basic;

public class User {
    private Integer id;
    private String name;
    private String password;
    private String sex;
    private Date birthday;
    private Date registTime;

    //无参构造(必备构造二选一)
    public User() {}
    
    //全参构造(必备构造二选一)
    public User(Integer id, String name, String password, String sex, Date birthday, Date registTime) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.sex = sex;
        this.birthday = birthday;
        this.registTime = registTime;
    }
    
    //Getters And Setters
}
3.2 Dao接口

根据所需DAO定义接口、以及方法

package com.qf.mybatis.part1.basic;

public interface UserDao {
    User selectUserById(Integer id);
}
3.3 MyBatis的API操作方式

操作步骤:

  • 获得读取MyBatis配置文件的流对象
  • 构建SqlSession连接对象的工厂
  • 通过工厂获得连接对象
  • 通过连接对象获得接口实现类对象
  • 调用接口中的方法
  • 关闭会话
package com.qf.mybatis.part1.basic;

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;

public class HelloMyBatis {

    @Test
    public void test1() throws IOException {
        //1.获得读取MyBatis配置文件的流对象
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");

        //2.构建SqlSession连接对象的工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);

        //3.通过工厂获得连接对象
        SqlSession sqlSession = factory.openSession();

        //4.通过连接对象获得接口实现类对象  
        UserDao userDao = sqlSession.getMapper(UserDao.class);

        //5.调用接口中的方法
        System.out.println(userDao.selectUserById(1));
    }
}

四、MyBatis核心操作

4.1 查询

标签:< select id="" resultType="" >

resultType:查询结果要封装成的类型,类的全限定名

序号参数绑定

public interface UserDao {
        //使用原生参数绑定
    public User selectUserByIdAndPwd(Integer id , String pwd);
}
<!--配置文件中的参数的序号与形参列表中的参数顺序相对应-->
<select id="selectUserByIdAndPwd" resultType="user">
    SELECT * FROM t_users
    WHERE id = #{arg0} AND password = #{arg1} <!--arg0 arg1 arg2 ...-->
</select>

<select id="selectUserByIdAndPwd" resultType="user">
  SELECT * FROM t_users
    WHERE id = #{param1} AND password = #{param2} <!--param1 param2 param3 ...-->
</select>

注解参数绑定(推荐参数较少时使用)

import org.apache.ibatis.annotations.Param; //引入注解

public interface UserDao {
    //使用MyBatis提供的@Param进行参数绑定
    public User selectUserByIdAndPwd(@Param("id") Integer id , @Param("pwd") String pwd);
}
<select id="selectUserByIdAndPwd" resultType="user">
    SELECT * FROM t_users
    WHERE id = #{id} AND password = #{pwd} <!-- 使用注解值 @Param("pwd") -->
</select>

Map参数绑定(不推荐使用)

public interface UserDao {
    //添加Map进行参数绑定
        public User selectUserByIdAndPwd_map(Map values);
}

Map values = new HashMap(); //测试类创建Map
values.put("myId",1); //自定义key,绑定参数
values.put("myPwd","123456");
User user = userDao.selectUserByIdAndPwd_map(values);
<select id="selectUserByIdAndPwd_map" resultType="user">
    SELECT * FROM t_users 
    WHERE id = #{myId} AND password = #{myPwd} <!-- 通过key获得value -->
</select>

对象参数绑定(推荐参数较多时使用)

public interface UserDao {
    //使用对象属性进行参数绑定
    public User selectUserByUserInfo(User user);
}
<select id="selectUserByUserInfo" resultType="user">
    SELECT * FROM t_users
    WHERE id = #{id} AND password = #{password} <!-- #{id}取User对象的id属性值、#{password}同理 -->
</select>

使用对象参数绑定进行添加数据的操作时,添加配置useGeneratedKeys="true" keyProperty="id" 可以设置传入对象的自增属性,不进行设置则对象属性的自增属性为默认值。

4.2 模糊查询
public interface UserDao {
    public List<User> selectUsersByKeyword(@Param("keyword") String keyword);
}
<mapper namespace="com.qf.mybatis.part1.different.UserDao">
    <select id="selectUsersByKeyword" resultType="user">
        SELECT * FROM t_users 
      WHERE name LIKE concat('%',#{keyword},'%') <!-- 拼接'%' -->
    </select>
</mapper>
4.3 删除

标签:< delete id="" parameterType="" >

parameterType:使用对象参数绑定时,使用的对象的类型,类的全限定名

<delete id="deleteUser" parameterType="int">
    DELETE FROM t_users
    WHERE id = #{id} <!--只有一个参数时,#{任意书写}-->
</delete>
4.4 修改

标签:< update id="" parameterType="" >

<update id="updateUser" parameterType="user">
    UPDATE t_users SET name=#{name}, password=#{password}, sex=#{sex}, birthday=#{birthday}
    WHERE id = #{id} <!--方法参数为对象时,可直接使用#{属性名}进行获取-->
</update>
4.5 添加

标签:< insert id="" parameterType="" >

<!--手动主键-->
<insert id="insertUser" parameterType="user">
    INSERT INTO t_users VALUES(#{id},#{name},#{password},#{sex},#{birthday},NULL);
</insert>

<!--自动主键-->
<insert id="insertUser" parameterType="user">
  <!-- 自动增长主键,以下两种方案均可 -->
  INSERT INTO t_users VALUES(NULL,#{name},#{password},#{sex},#{birthday},NULL);
</insert>
4.6 主键回填

主键回填:
将新数据的ID,存入Java对象的和主键对应的属性中
useGeneratedKeys设置为true,同时通过keyProperty设置主键对应的属性

注意:要是想要得到自增的主键值,需要参数为对象且有对应的主键属性

<!--    新增语句  id属性的值对应dao中的方法名 必须一致-->
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        insert into t_user(nickname,phone,pass,ctime) values(#{nickname},#{phone},#{pass},now());
    </insert>
4.6.1 通过last_insert_id()查询主键

标签:< selectKey keyProperty="" resultType="" order=“AFTER|BEFORE”>< /selectKey>

create table t_product(
	id int primary key auto_increment,
    name varchar(50)
)default charset = utf8;
class Product{
    private Integer id;
    private String name;
    //set+get...
}
<mapper namespace="com.qf.mybatis.part1.basic.ProductDao">
	<insert id="insertProduct" parameterType="product">
        <selectKey keyProperty="id" resultType="int" order="AFTER"><!--插入之后-->
            select last_insert_id()<!--适用于整数类型自增主键-->
        </selectKey>
        insert into t_product(id,name) values(#{id},#{name})
    </insert>
</mapper>
4.6.2 通过uuid()查询主键
create table t_order(
	id varchar(32) primary key,#字符型主键
    name varchar(50)
)default charset=utf8;
class Order{
    private Integer id;
    private String name;
    //set+get...
}
<mapper namespace="com.qf.mybatis.part1.basic.OrderDao">
	<insert id="insertOrder" parameterType="order">
    	<selectKey keyProperty="id" resultType="String" order="BEFORE"><!--插入之前-->
        	select replace(uuid(),'-','')<!--适用于字符类型主键-->
        </selectKey>
        insert into t_order(id,name) values(#{id},#{name})
    </insert>
</mapper>
4.7 封装MyBatis工具类
  • Resource:用于获得读取配置文件的IO对象,耗费资源,建议通过IO一次性读取所有所需要的数据。
  • SqlSessionFactory:SqlSession工厂类,内存占用多,耗费资源,建议每个应用只创建一个对象。
  • SqlSession:相当于Connection,可控制事务,应为线程私有,不被多线程共享。
  • 将获得连接、关闭连接、提交事务、回滚事务、获得接口实现类等方法进行封装。
package com.qf.mybatis.part1.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;

public class MyBatisUtils {

    //获得SqlSession工厂
    private static SqlSessionFactory factory;

    //创建ThreadLocal绑定当前线程中的SqlSession对象
    private static final ThreadLocal<SqlSession> tl = new ThreadLocal<SqlSession>();

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

    //获得连接(从tl中获得当前线程SqlSession)
    private static SqlSession openSession(){
        SqlSession session = tl.get();
        if(session == null){
            session = factory.openSession();
            tl.set(session);
        }
        return session;
    }

    //释放连接(释放当前线程中的SqlSession)
    private static void closeSession(){
        SqlSession session = tl.get();
        session.close();
        tl.remove();
    }

    //提交事务(提交当前线程中的SqlSession所管理的事务)
    public static void commit(){
        SqlSession session = openSession();
        session.commit();
        closeSession();
    }

    //回滚事务(回滚当前线程中的SqlSession所管理的事务)
    public static void rollback(){
        SqlSession session = openSession();
        session.rollback();
        closeSession();
    }

    //获得接口实现类对象
    public static <T extends Object> T getMapper(Class<T> clazz){
        SqlSession session = openSession();
        return session.getMapper(clazz);
    }
}
4.8 数据库连接池

引入Druid依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>

MyDruidDataSourceFactory继承PooledDataSourceFactory,并替换数据源:

package com.qf.mybatis.part2.utils;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.pooled.PooledDataSourceFactory;

public class MyDruidDataSourceFactory extends PooledDataSourceFactory {
    public MyDruidDataSourceFactory() {
        this.dataSource = new DruidDataSource();//替换数据源
    }
}

mybatis-config.xml中连接池相关配置:

<!--连接池-->
            <dataSource type="com.qfedu.mybatis.config.DruidDataSourceFactory">
                <!--连接数据库的四要素 -->
                <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
                <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db_biggod?serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
                <property name="initialSize" value="2"/>
                <property name="maxActive" value="100"/>
            </dataSource>

注意:< property name=“属性名” />属性名必须与com.alibaba.druid.pool.DruidAbstractDataSource中一致

Druid的常用参数:

配置缺省值说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:“DataSource-”+System.identityHashCode(this)
url连接数据库的url,不同数据库不一样。例如:mysql:jdbc:mysql://10.20.153.104:3306/druid2
oracle:jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置问价中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPrepareStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义:
1) Destroy线程会检测连接的间隔时间2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

五、MyBatis配置细节

5.1 mapper.xml放在resources外路径中的读取问题

在pom.xml文件最后追加< build >标签,以便可以将xml文件复制到classes中,并在程序运行时正确读取。

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>*.xml</include><!--默认(新添加自定义则失败)-->
                <include>**/*.xml</include><!--新添加*/代表1级目录 **/代表多级目录-->
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>
5.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">

<!--MyBatis配置-->
<configuration>

    <!--添加properties配置文件路径(外部配置、动态替换)-->
    <properties resource="jdbc.properties" />

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

    <!--定义别名二选一(注意标签书写顺序)-->
    <typeAliases>
        <!--定义类的别名-->
        <typeAlias type="com.zjl.mybatis.entity.Student" alias="student"/>
        
        <!--自动扫描包,将原类名作为别名-->
        <package name="com.zjl.mybatis.entity"/>
    </typeAliases>
    
    ...
</configuration>
5.3 整合log4j日志

pom.xml添加log4j依赖

<!-- log4j日志依赖 https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

创建并配置log4j.properties

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
级别描述
ALL LEVEL打开所有日志记录;是最低等级的,用于打开所有日志记录。
DEBUG输出调试信息;指出细粒度信息事件对调试应用程序是非常有帮助的。
INFO输出提示信息;消息在粗粒度级别上突出强调应用程序的运行过程。
WARN输出警告信息;表明会出现潜在错误的情形。
ERROR输出错误信息;指出虽然发生错误事件,但仍然不影响系统的继续运行。
FATAL输出致命错误;指出每个严重的错误事件将会导致应用程序的退出。
OFF LEVEL关闭所有日志记录开关;是最高等级的,用于关闭所有日志记录。

六、缓存

6.1 缓存设计

内存中的一块存储空间,服务与某个应用,旨在将频繁读取的数据临时保存在内存中,便于二次快速访问。

一级缓存

二级缓存

6.2 一级缓存

一级缓存是SqlSession级别的缓存,同一个SqlSession的发起多次同构查询,会将数据保存在一级缓存中。在操作数据库时需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间的缓存数据区域(HashMap)是相互不影响的。

注意:无需任何配置,默认开启一级缓存

一级缓存修改

6.3 二级缓存

SqlSessionFactory级别的缓存,同一个SqlSessionFactory构建的SqlSession发起的多次同构查询,会将数据保存在二级缓存中

注意:在sqlSession.commit()或者sqlSession.close()之后生效开启全局缓存

< settings >是MyBatis中极为重要的调整设置,他们会改变MyBatis的运行行为,其他详细配置可参考官方文档:

<configuration>
  <properties .../>
    
    <!-- 注意书写位置 -->
    <settings>
        <setting name="cacheEnabled" value="true"/> <!-- mybaits-config.xml中开启全局缓存(默认开启) -->
    </settings>
  
    <typeAliases></typeAliases>
</configuration>

指定Mapper缓存

<mapper namespace="com.qf.mybatis.part2.cache.BookDao">
      <!--进行二级缓存的配置:
    1.size 缓存的对象个数 默认1024
    2.flushInterval 缓存中对象的有效期 如果缓存足够,有效期无效
    3.eviction 缓存的淘汰策略 默认LRU(最近最少使用原则) FIFO(先进先出)-->
    <cache size="1204" flushInterval="1000" eviction="LRU"></cache>

    <select id="selectBookByCondition" resultType="com.qf.mybatis.part2.cache.Book">
        SELECT * FROM t_books
    </select>
</mapper>
@Test
public void testMapperCache(){

    SqlSession sqlSession1 = MyBatisUtils.getSession();
  
    BookDao bookDao1 = sqlSession1.getMapper(BookDao.class);

    bookDao1.selectBookByCondition(new Book());

    sqlSession1.close(); //必须关闭SqlSession才可缓存数据

    //--------------------

    SqlSession sqlSession2 = MyBatisUtils.getSession();

    BookDao bookDao2 = sqlSession2.getMapper(BookDao.class);

    bookDao2.selectBookByCondition(new Book());

    sqlSession2.close(); //缓存击中
}

缓存清空并重新缓存:

@Test
public void testMapperCache(){

    SqlSession sqlSession1 = MyBatisUtils.getSession();
  
    BookDao bookDao1 = sqlSession1.getMapper(BookDao.class);

    bookDao1.selectBookByCondition(new Book());

    sqlSession1.close(); //必须关闭SqlSession才可缓存数据

    //--------------------
    
        SqlSession sqlSession3 = MyBatisUtils.getSession();

        BookDao bookDao3 = sqlSession3.getMapper(BookDao.class);

        bookDao3.deleteBookById(102);

        sqlSession3.commit(); //DML成功,数据发生变化,缓存清空

        sqlSession3.close();
  
    //--------------------

    SqlSession sqlSession2 = MyBatisUtils.getSession();

    BookDao bookDao2 = sqlSession2.getMapper(BookDao.class);

    bookDao2.selectBookByCondition(new Book());

    sqlSession2.close(); //缓存未击中,重新查询数据库、重新缓存
}

二级缓存结果

七、MyBatis进阶

7.1 ORM失效或ResultType和ResultMap

自动ORM失效

方案一:列的别名
在SQL中使用as为查询字段添加列别名,以匹配属性名:

<mapper namespace="com.qf.mybatis.part2.orm.ManagerDao">
    <select id="selectManagerByIdAndPwd" resultType="com.qf.mybatis.part2.orm.Manager">
        SELECT mgr_id AS id , mgr_name AS username , mgr_pwd AS password
        FROM t_managers
        WHERE mgr_id = #{id} AND mgr_pwd = #{pwd}
    </select>
</mapper>

方案二:结果映射(ResultMap-查询结果的封装规则)
通过< resultMap id="" type="" >映射,匹配列名与属性名:

<mapper namespace="com.qf.mybatis.part2.orm.ManagerDao">

    <!--定义resultMap标签-->
    <resultMap id="managerResultMap" type="com.qf.mybatis.part2.orm.Manager">
        <!--关联主键与列名-->
        <id property="id" column="mgr_id" />
      
        <!--关联属性与列名-->
        <result property="username" column="mgr_name" />
        <result property="password" column="mgr_pwd" />
    </resultMap>
  
     <!--使用resultMap作为ORM映射依据-->
    <select id="selectAllManagers" resultMap="managerResultMap">
        SELECT mgr_id , mgr_name , mgr_pwd
        FROM t_managers
    </select>
</mapper>
7.2 OneToOne

实体间的关系:关联关系(拥有has、属于belong)

  • OneToOne:一对一关系(Passenger—Passport)
  • OneToMany:一对多关系(Employee—Department)
  • ManyToMany:多对多关系(Student—Subject)

OneToOne

create table t_student(id int primary key auto_increment,no varchar(30),name varchar(20));
create table t_studentdetail(id int primary key auto_increment,sno varchar(30),sex int,birthday date);
create table t_course(id int primary key auto_increment,name varchar(30));
create table t_studentcourse(id int primary key auto_increment,sno varchar(30),cid int,ctime datetime);

Table建立外键关系

Entity添加关系属性

\Mapper中将属性与列名对应

实现方案1:联合查询+association

<mapper namespace="com.qf.mybatis.part2.one2one.PassengerDao">

    <!-- 结果映射(查询结果的封装规则) -->
    <resultMap id="passengerResultMap" type="com.qf.mybatis.part2.one2one.Passenger">
        <id property="id" column="id"/>
        <result property="name" column="name" />
        <result property="sex" column="sex" />
        <result property="birthday" column="birthday" />

        <!-- 关系表中数据的封装规则 -->   <!-- 指定关系表的实体类型 -->
        <association property="passport" javaType="com.qf.mybatis.part2.one2one.Passport">
            <id property="id" column="passport_id" />
            <result property="nationality" column="nationality" />
            <result property="expire" column="expire" />
            <result property="passenger_id" column="passenger_id" />
        </association>
    </resultMap>

    <!-- 多表连接查询 -->             <!-- 结果映射(查询结果的封装规则)-->
    <select id="selectPassengerById" resultMap="passengerResultMap">
        <!-- 别名(避免与p1.id冲突) -->
        SELECT p1.id , p1.name , p1.sex , p1.birthday , p2.id as passport_id , p2.nationality , p2.expire       , p2.passenger_id
        FROM t_passengers p1 LEFT JOIN t_passports p2
        ON p1.id = p2.passenger_id
        WHERE p1.id = #{id}
    </select>
</mapper>

注意:指定“一方”关系时(对象),使用< association javaType="" >

实现方案2:联合查询+结果封装

public class StudentDetailDto {
    private Integer id;
    private String no;
    private String name;
    private Integer sex;
    private Date birthday;
    ……
}
//查询学生信息-详细信息
List<StudentDetailDto> all();
  <select id="all" resultType="com.qfedu.plus.dto.StudentDetailDto">
        select s.*,sd.sex,sd.birthday from t_student s inner join 
      t_studentdetail sd on s.no=sd.sno order by s.id desc;
    </select>
7.3 OneToMany
<mapper namespace="com.qf.mybatis.part2.one2many.DepartmentDao">

    <!-- 封装规则 -->
    <resultMap id="departmentResultMap" type="com.qf.mybatis.part2.one2many.Department">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="location" column="location" />
        
        <!-- 关系表中数据的封装规则 -->    <!-- 指定关系表的实体类型 -->
        <collection property="emps" ofType="com.qf.mybatis.part2.one2many.Employee">
            <id property="id" column="emp_id" />
            <result property="name" column="emp_name" />
            <result property="salary" column="salary" />
            <result property="dept_id" column="dept_id" />
        </collection>
    </resultMap>

    <!-- 多表连接查询 -->           <!-- 封装规则 -->
    <select id="selectDepartmentById" resultMap="departmentResultMap" >
        <!-- 别名(避免与d.id、d.name冲突)-->
        SELECT d.id , d.name , d.location , e.id AS emp_id , e.name emp_name , e.salary , e.dept_id
        FROM t_departments d LEFT JOIN t_employees e
        ON d.id = e.dept_id
        WHERE d.id = #{id}
    </select>

</mapper>

注意:指定“多方”关系时(集合),使用< collection ofType="" >

7.4 ManyToMany

建立第三张关系表

<mapper namespace="com.qf.mybatis.part2.many2many.StudentDao">

    <!-- 映射查询只封装两表中的信息,可忽略关系表内容 -->
    <resultMap id="allMap" type="com.qf.mybatis.part2.many2many.Student">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="sex" column="sex" />
        <collection property="subjects" ofType="com.qf.mybatis.part2.many2many.Subject">
            <id property="id" column="sid" />
            <result property="name" column="sname" />
            <result property="grade" column="grade" />
        </collection>
    </resultMap>

    <!-- 三表连接查询 -->
    <select id="selectAllStudents" resultMap="allMap">
        SELECT s1.* , ss.* , s2.id as sid , s2.name as sname , s2.grade
        FROM t_students s1 LEFT JOIN t_stu_sub ss
        ON s1.id = ss.student_id <!-- 通过t_stu_sub表建立二者之间的关系 -->
        LEFT JOIN t_subjects s2
        ON ss.subject_id = s2.id
    </select>
</mapper>

注意:指定“多方”关系时(集合),使用< collection ofType="" >一方,添加集合;多方,添加对象。双方均可建立关系属性,建立关系属性后,对应的Mapper文件中需使用< ResultMap >完成多表映射。持有对象关系属性,使用< association property=“dept” javaType=“department” >持有集合关系属性,使用< collection property=“emps” ofType=“employee” >

7.5 动态SQL

yBatis的映射文件中支持在基础SQL上添加一些逻辑操作,并动态拼接成完整的SQL之后再执行,以达到SQL复用、简化编程的效果

< sql >

<mapper namespace="com.qf.mybatis.part2.dynamic.BookDao">
    <sql id="BOOKS_FIELD"> <!-- 定义SQL片段 -->
        SELECT id,name,author,publish,sort
    </sql>

    <select id="selectBookByCondition" resultType="com.qf.mybatis.part2.dynamic.Book">
        <include refid="BOOKS_FIELD" /> <!-- 通过ID引用SQL片段 -->
        FROM t_books
    </select>
</mapper>

< where >

<select id="selectBookByCondition" resultType="com.qf.mybatis.part2.dynamic.Book">
    SELECT id , name , author , publish , sort
    FROM t_books
    <where> 
        <if test="id != null"> <!-- WHERE,会自动忽略前后缀(如:and | or) -->
            id = #{id}
        </if>

        <if test="name != null">
            and name = #{name}
        </if>

        <if test="author != null">
            and author = #{author}
        </if>

        <if test="publish != null">
            and publish = #{publish}
        </if>

        <if test="sort != null">
            and sort = #{sort}
        </if>
    </where>
</select>

< set >

<update id="updateBookByCondition">
    UPDATE t_books
    <set>
        <if test="name != null"><!-- where子句中满足条件的if,会自动忽略后缀(如:,) -->
            name = #{name} ,
        </if>

        <if test="author != null">
            author = #{author} ,
        </if>

        <if test="publish != null">
            publish = #{publish} ,
        </if>

        <if test="sort != null">
            sort = #{sort} ,
        </if>
    </set>
    WHERE id = #{id}
</update>

< trim >

< trim prefix="" suffix="" prefixOverrides="" suffixOverrides="" >代替< where > 、< set >

<select id="selectBookByCondition" resultType="com.qf.mybatis.day2.dynamic.Book">
		SELECT id,name,author,publish,sort
    FROM t_books
    <trim prefix="WHERE" prefixOverrides="AND|OR"> <!-- 增加WHERE前缀,自动忽略前缀 -->
        <if test="id != null">
            and id = #{id}
        </if>

        <if test="name != null">
            and name = #{name}
        </if>

        <if test="author != null">
            and author = #{author}
        </if>

        <if test="publish != null">
            and publish = #{publish}
        </if>

        <if test="sort != null">
            and sort = #{sort}
        </if>
		</trim>
</select>
<update id="updateBookByCondition">
		UPDATE t_books
		<trim prefix="SET" suffixOverrides=","> <!-- 增加SET前缀,自动忽略后缀 -->
				<if test="name != null">
						name = #{name} ,
				</if>

				<if test="author != null">
						author = #{author} ,
				</if>

				<if test="publish != null">
						publish = #{publish} ,
				</if>

				<if test="sort != null">
						sort = #{sort}
				</if>
    </trim>
		WHERE id = #{id}
</update>

< foreach >

<delete id="deleteBookByIds">
		DELETE FROM t_books
		WHERE id IN
		<foreach collection="list" open="(" separator="," close=")"  item="id" index="i">
				#{id}
		</foreach>
</delete>
参数描述取值
collection容器类型list、array、map
open起始符(
close结束符)
separator分隔符,
index下标号从0开始,依次递增
item当前项任意名称(循环中通过#{任意名称}表达式访问)

八、PageHelper

8.1 概念

PageHelper是适用于MyBatis框架的一个分页插件,使用方式极为便捷,支持任何复杂的单表、多表分页查询操作。

8.2 访问与下载

官方网站:https://pagehelper.github.io/

下载地址:https://github.com/pagehelper/Mybatis-PageHelper

8.3 开发步骤

PageHelper中提供了多个分页操作的静态方法入口。

8.3.1 引入依赖

pom.xml中引入PageHelper依赖。

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
8.3.2 配置mybatis-config.xml

在mybatis-config.xml中添加< plugins>。

<configuration>
    <typeAliases></typeAliases>
  
    <plugins>
        <!-- com.github.pagehelper为PageHelper类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
  
    <environments>...</environments>
</configuration>
8.3.3 PageHelper应用方式

使用PageHelper提供的静态方法设置分页查询条件。

@Test
public void test(){
    StudentDao studentDao = MBUtils.getMapper(StudentDao.class);
    PageHelper.startPage(1,5);//使用PageHelper设置分页条件
    List<Student> students = studentDao.selectAll();
    for (Student student : students) {
        System.out.println(student);
    }
}
8.4 PageInfo对象

PageInfo对象中包含了分页操作中的所有相关数据。

PageInfo结构图

8.4.1 PageInfo应用方式

使用PageInfo保存分页查询结果。

@Test
public void test(){
    StudentDao studentDao = MBUtils.getMapper(StudentDao.class);
    PageHelper.startPage(1,5);
    List<Student> students = studentDao.selectAll();
    PageInfo<Student> studentPageInfo = new PageInfo<>(students);//将分页查询的结果集保存在PageInfo对象中
    System.out.println(pageInfo);
}
8.4.2 注意事项
  • 只有在PageHelper.startPage()方法之后的第一个查询会有执行分页
  • 分页插件不支持带有"for update"的查询语句。
  • 分页插件不支持"嵌套查询",由于嵌套结果方式会导致结果集被折叠,所以无法保证分页结果数量正确。

九、MyBatis其他相关

9.1 MyBatis注解操作

通过在接口中直接添加Mybatis注解,完成CRUD。

  • 注意:接口注解定义完毕后,需将接口全限定名注册到mybatis-config.xml的< mappers>中
  • 经验:注解模式属于硬编码到.java文件中,失去了使用配置文件外部修改的优势,可结合需求选用。
<mappers>
    <mapper class="com.qf.mybatis.part1.annotations.UserMapper" /><!-- class="接口全限定名"-->
</mappers>
9.1.1 查询
public interface UserMapper {
    @Select("SELECT * FROM t_users WHERE id = #{id}")
    public User selectUserById(Integer id);

    @Select("SELECT * FROM t_users WHERE id = #{id} AND password = #{pwd}")
    public User selectUserByIdAndPwd_annotation(@Param("id") Integer id, @Param("pwd") String password);
}
9.1.2 删除
@Delete(value = "DELETE FROM t_users WHERE id = #{id}")
public int deleteUser(Integer id);
9.1.3 修改
@Update("UPDATE t_users SET name = #{name} , password = #{password} , salary = #{salary} , birthday = #{birthday} WHERE id = #{id}")
public int updateUser(User user);
9.1.4 插入
@Insert("INSERT INTO t_users VALUES(#{id},#{name},#{password},#{salary},#{birthday},null)")
public int insertUser(User user);

@Options(useGeneratedKeys = true , keyProperty = "id") // 自增key,主键为id
@Insert("INSERT INTO t_users VALUES(#{id},#{name},#{password},#{salary},#{birthday},null)")
public int insertUserGeneratedKeys(User user);
9.2 $符号的应用场景

${attribute}属于字符串拼接SQL,而非预编译占位符,会有注入攻击问题,不建议在常规SQL中使用,常用于解决动态升降序问题。

9.2.1 $符号参数绑定
public List<User> selectAllUsers1(User user); // ${name} ${id} 可获取user中的属性值
public List<User> selectAllUsers2(@Param("rule") String rule); //必须使用@Param否则会作为属性解析
<select id="selectAllUsers1" resultType="user">
	SELECT * FROM t_users 
    WHERE name = '${name}' or id = ${id} <!-- 拼接name和id,如果是字符类型需要用单引号:'${name}' -->
</select>
<select id="selectAllUsers2" resultType="user">
	SELECT * FROM t_users 
  	ORDER BY id ${rule} <!-- 拼接 asc | desc -->
</select>
User user = new User(....);
List<User> ulist1 = userDAO.selectAllUsers1(user); //调用时传入user对象

List<User> ulist2 = userDao.selectAllUsers2("desc"); //调用时传入asc | desc
9.2.2 $符号注入攻击
<select id="selectUsersByKeyword" resultType="user">
	SELECT * FROM t_user
  	WHERE name = '${name}' <!-- 会存在注入攻击  比如传入参数是 【String name = "tom' or '1'='1";】-->
</select>

sql注入攻击

9.3 MyBatis处理关联关系-嵌套查询

思路:查询部门信息时,级联查询所属的员工信息。

  • DepartmentDao接口中定义selectDepartmentById,并实现Mapper。
  • EmployeeDao接口中定义selectEmployeesByDeptId,并实现Mapper。
  • 当selectDepartmentById被执行时,通过< collection>调用selectEmployeesByDeptId方法,并传入条件参数。
9.3.1 主表查询

定义selectEmployeesByDeptId,并书写Mapper,实现根据部门ID查询员工信息

public interface EmployeeDao {
    /**
     * 根据部门编号查询员工信息
     * @param did 部门编号
     * @return 该部门中的所有员工
     */
    public List<Employee> selectEmployeeByDeptId(@Param("did") String did);
}
<mapper namespace="com.qf.mybatis.part2.one2many.EmployeeDao">
    <!-- 根据部门编号查询所有员工 -->
    <select id="selectEmployeeById" resultType="employee" >
        SELECT id,name,salary,dept_id 
      	FROM t_employees 
      	WHERE dept_id = #{did}
    </select>
</mapper>
9.3.2 级联调用

定义selectDepartmentById,并书写Mapper,实现根据部门ID查询部门信息,并级联查询该部门员工信息

public interface DepartmentDao {
    /**
     * 查询部门信息
     * @param id
     * @return
     */
    public Department selectDepartmentById(@Param("id") String id);
}
<mapper namespace="com.qf.mybatis.part2.one2many.DepartmentDao">
    <resultMap id="departmentResultMap" type="department">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="location" column="location" />
         <!-- column="传入目标方法的条件参数"  select="及联调用的查询目标"-->
        <collection property="emps" ofType="Employee" column="id" 
                    select="com.qf.mybatis.part2.one2many.EmployeeDao.selectEmployeeByDeptId" />
    </resultMap>
    <select id="selectAllDepartments" resultMap="departmentResultMap">
        SELECT id , name , location
        FROM t_departments
        WHERE id = #{id}
    </select>
</mapper>
9.3.3 延迟加载

mybatis-config.xml中开启延迟加载

<settings>
		<setting name="lazyLoadingEnabled" value="true"/> <!-- 开启延迟加载(默认false) -->
</settings>
  • 注意:开启延时加载后,如果不使用级联数据,则不会触发级联查询操作,有利于加快查询速度、节省内存资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

周景洛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值