MyBatis详细笔记

导学

文章目录

一、MyBatis介绍

1. 什么是框架?

image-20220412215711310

框架即一个半成品软件。开发者从头开发一个软件需要花费大量精力,于是有一些项目组开发出半成品软件,开发者在这些软件的基础上进行开发,这样的软件就称之为框架。

如果将开发完成的软件比作是一套已经装修完毕的新房,框架就好比是一套已经修建好的毛坯房。用户直接购买毛坯房,保证建筑质量和户型合理的同时可以进行风格的自由装修。

使用框架开发的好处:

  1. 省去大量的代码编写、减少开发时间、降低开发难度。
  2. 限制程序员必须使用框架规范开发,增强代码的规范性,降低程序员之间沟通及日后维护的成本。
  3. 将程序员的注意力从技术中抽离出来,更集中在业务层面。

使用框架就好比和世界上最优秀的软件工程师共同完成一个项目,并且他们完成的还是基础、全局的工作。

2. 什么是ORM框架?

image-20220412215914323

ORM(Object Relationl Mapping),对象关系映射,即在数据库和对象之间作映射处理。

之前我们使用JDBC操作数据库,必须手动进行数据库和对象间的数据转换。

public class test {
    // 新增方法,将对象转为sql语句字段
    public void AddUser(User user) throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
        String sql = "INSERT INTO user values (null,?,?,?,?)";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, user.getName());
        preparedStatement.setInt(2, user.getAge());
        preparedStatement.setString(3, user.getAddress());
        preparedStatement.setString(4, user.getSex());
        preparedStatement.executeUpdate();
        // 省略资源关闭...
    }

    // 查询方法,将数据库结果集转为对象
    public List<User> findAllUser() throws Exception {
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root","root");
        PreparedStatement preparedStatement = connection.prepareStatement("select * from user");
        ResultSet resultSet = preparedStatement.executeQuery();
        //遍历查询结果集
        List<User> users = new ArrayList<>();
        while (resultSet.next()) {
            // 拿到每一列数据
            int id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            String address = resultSet.getString("address");
            String sex = resultSet.getString("sex");
            // 将数据封装到对象中
            User user = new User();
            user.setId(id);
            user.setName(name);
            user.setAge(age);
            user.setAddress(address);
            user.setSex(sex);
            users.add(user); 
        }
        // 省略资源关闭...
        return users;
    }
}

这段代码中,数据库数据与对象数据的转换代码繁琐、无技术含量。而使用ORM框架代替JDBC后,框架可以帮助程序员自动进行转换,只要像平时一样操作对象,ORM框架就会根据映射完成对数据库的操作,极大的增强了开发效率。

3. 什么是MyBatis?

image-20220412220234706

MyBatis是一个半自动的ORM框架,其本质是对JDBC的封装。使用MyBatis不需要写JDBC代码,但需要程序员编写SQL语句。之前是apache的一个开源项目iBatis,2010年改名为MyBatis。

补充:
Hibernate也是一款持久层ORM框架,多年前的市场占有率很高,但近年来市场占有率越来越低。

MyBatis与Hibernate的比较:

  • MyBatis是一个半自动的ORM框架,需要手写SQL语句。
  • Hibernate是一个全自动的ORM框架,不需要手写SQL语句。
  • 使用MyBatis的开发量要大于Hibernate。

为什么Hibernate市场占有率越来越低:

  • 对于新手学习Hibernate时间成本比MyBatis大很多,MyBatis上手很快。
  • Hibernate不需要写SQL语句是因为框架来生成SQL语句。对于复杂查询,开发者很难控制生成的SQL语句,这就导致SQL调优很难进行。
  • 之前的项目功能简单,数据量小,所以使用Hibernate可以快速完成开发。而近年来项目的数据量越来越大,而互联网项目对查询速度要求也很高,这就要求我们一定要精细化的调整SQL语句。此时灵活性更强,手动编写SQL语句的MyBatis慢慢代替了Hibernate使用。
  • 在高并发、大数据、高性能、高响应的互联网项目中,MyBatis是首选的持久框架。而对于对性能要求不高的比如内部管理系统等可以使用Hibernate。

二、MyBatis入门

image-20220412220521578

1. 环境搭建

  1. 将SQL文件导入数据库

  2. 创建maven工程,引入依赖

    <dependencies>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <!--mysql驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--log4j日志-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
    </dependencies>
    
  3. 创建mybatis核心配置文件SqlMapConfig.xml

    如果dtd报红色,可以在设置中搜索dtds新增该dtd

    &amp;是&的转义

    <?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="mysql">
            <environment id="mysql">
                <!--事务类型-->
                <transactionManager type="JDBC"></transactionManager>
                <!--数据源-->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql:///mybatis?useUnicode=true&amp;characterEncoding=utf8"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    </configuration>
    
  4. 将log4j.properties文件放入resources中,让控制台打印SQL语句。

    # Set root category priority to INFO and its only appender to CONSOLE.
    #log4j.rootCategory=INFO, CONSOLE            debug   info   warn error fatal
    log4j.rootCategory=debug, CONSOLE
    
    # Set the enterprise logger category to FATAL and its only appender to CONSOLE.
    #log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
    
    # CONSOLE is set to be a ConsoleAppender using a PatternLayout.
    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern=[%d{MM/dd HH:mm:ss}] %-6r [%15.15t] %-5p %30.30c %x - %m\n
    
  5. 创建实体类

    package com.sxt.pojo;
    
    public class User {
        private int id;
        private String username;
        private String sex;
        private String address;
    
        public User() {
        }
    
        public User(int id, String username, String sex, String address) {
            this.id = id;
            this.username = username;
            this.sex = sex;
            this.address = address;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", sex='" + sex + '\'' +
                    ", address='" + address + '\'' +
                    '}';
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        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;
        }
    }
    

2. 创建持久层接口和映射文件

  1. 在java目录创建持久层接口

    public interface UserMapper {
        List<User> findAll();
    }
    
  2. 在resource目录创建映射文件

    <?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.sxt.mapper.UserMapper">
        <select id="findAll" resultType="com.sxt.pojo.User">
            select * from user
        </select>
    </mapper>
    
  3. 将映射文件配置到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="mysql">
            <environment id="mysql">
                <!--事务类型-->
                <transactionManager type="JDBC"></transactionManager>
                <!--数据源-->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql///mybatis"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
        <!--注册映射文件-->
        <mappers>
            <mapper resource="com/sxt/mapper/UserMapper.xml"></mapper>
        </mappers>
    </configuration>
    

映射文件注意事项:

  • 映射文件要和接口名称相同。

  • 映射文件要和接口的目录结构相同。

    image-20220413095948932

  • 映射文件中namespace属性要写接口的全名。名称空间,对应的接口的全限定名

  • 映射文件中标签的id属性是接口方法的方法名。

  • 映射文件中标签的resultType属性是接口方法的返回值类型。

  • 映射文件中标签的parameterType属性是接口方法的参数类型。

  • 映射文件中resultType、parameterType属性要写全类名,如果是集合类型,则写其泛型的全类名

3. 测试持久层接口方法

import com.sxt.mapper.UserMapper;
import com.sxt.pojo.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.List;

public class TestUserMapper {
    @Test
    public void testFindAll() throws IOException {
        //【1】读取核心配置文件
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //【2】创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //【3】SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
        SqlSessionFactory factory = builder.build(is);
        //【4】SqlSessionFactory对象获取SqlSession对象
        SqlSession session = factory.openSession();
        //【5】SqlSession对象获取代理对象
        UserMapper mapper = session.getMapper(UserMapper.class);
        //【6】代理对象执行方法
        List<User> list = mapper.findAll();
        list.forEach(System.out::println);
        //【7】释放资源
        session.close();
        is.close();
    }
}

4. MyBatis核心对象及工作流程

image-20220412221903866

4.1 MyBatis核心对象

  • SqlSessionFactoryBuilder

    SqlSession工厂构建者对象,使用构造者模式创建SqlSession工厂对象。

  • SqlSessionFactory

    SqlSession工厂,使用工厂模式创建SqlSession对象。

  • SqlSession

    该对象可以操作数据库,也可以使用动态代理模式创建持久层接口的代理对象操作数据库。

  • Mapper

    持久层接口的代理对象,他具体实现了持久层接口,用来操作数据库。

4.2 MyBatis工作流程

  1. 创建SqlSessionFactoryBuilder对象
  2. SqlSessionFactoryBuilder对象构建了SqlSessionFactory对象:构造者模式
  3. SqlSessionFactory对象生产了SqlSession对象:工厂模式
  4. SqlSession对象创建了持久层接口的代理对象:动态代理模式
  5. 代理对象操作数据库

5. 使用SqlSession操作数据库

除了代理对象能够操作数据库,SqlSession也能操作数据库。只是这种方式在开发中使用的较少,接下来我们使用SqlSession操作数据库:

SqlSession调用的方法中参数应传入接口的全类名和方法名

@Test
public void testFindAll2() throws IOException {
    //【1】读取核心配置文件
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
    //【2】创建SqlSessionFactoryBuilder对象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //【3】SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
    SqlSessionFactory factory = builder.build(is);
    //【4】SqlSessionFactory对象获取SqlSession对象
    SqlSession session = factory.openSession();
    //【5】SqlSession直接操作数据库  需要传入接口的全类名和方法名
    List<User> list = session.selectList("com.sxt.mapper.UserMapper.findAll");
    list.forEach(System.out::println);
    //【6】关闭资源
    session.close();
    is.close();
}

6. Mapper动态代理原理

image-20220412222148887

接下来我们通过源码,了解MyBatis的Mapper对象究竟是怎么生成的,他又是如何代理接口的方法。

6.1 获取代理对象

点开测试类的 getMapper 方法,查看该方法最终调用了什么方法。

image-20220412222257421

当看到 Proxy.newProxyInstance 时,可以确定 getMapper 方法最终调用的是JDK动态代理方法,且使用MapperProxy类定义代理方式

6.2 查看代理方式

点开MapperProxy类,查看invoke方法,查看代理对象是如何工作的。

image-20220412222333945

可以看到,MapperProxy调用了MapperMethod的execute方法定义了代理方式,且底层调用的是SqlSession的方法,根据映射文件标签不同调用不同的SqlSession方法。

结论:

  • SqlSession的getMapper方法,最终是调用的是JDK动态代理方法,生成一个代理对象,类型就是传入的接口类型。
  • MapperProxy对象通过调用MapperMethod的execute方法定义了代理方式,该方法的底层调用的是SqlSession的方法。

三、MyBatis增删改查

1. MyBatis新增

image-20220413111821123

新增用户

  1. 持久层接口添加方法

    void add(User user);
    
  2. 映射文件添加标签

    <insert id="add" parameterType="com.sxt.pojo.User" >
        insert into user(username,sex,address) values(#{username},#{sex},#{address})
    </insert>
    
  3. 编写测试方法

    @Test
    public void testAdd() throws Exception{
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        SqlSession sqlSession = factory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User("哈哈", "男", "天津");
        mapper.add(user);
        //默认事务是不会自动提交的
        sqlSession.commit();
    
        sqlSession.close();
        is.close();
    }
    

注意:

  1. 当接口方法的参数类型为POJO类型时,SQL语句中绑定参数时使用 #{POJO的属性名} 即可。
  2. MyBatis事务默认手动提交,所以在执行完增删改方法后,需要手动调用SqlSession对象的事务提交方法,否则数据库将不发生改变。
  3. dml操作需要手动提交事务,查询则不需要手动提交,因为提交并没有改变数据库

2. MyBatis修改

优化测试类

我们发现MyBatis的测试方法在操作数据库前都需要获取代理对象,操作数据库后都需要释放资源,可以利用Junit的前置后置方法,优化测试类代码。

import com.sxt.mapper.UserMapper;
import com.sxt.pojo.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.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

public class TestUserMapper2 {
    InputStream is = null;
    SqlSession session = null;
    UserMapper mapper = null;
    
    @Before
    public void before() throws IOException {
        //【1】读取核心配置文件
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //【2】创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //【3】SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
        SqlSessionFactory factory = builder.build(is);
        //【4】SqlSessionFactory对象获取SqlSession对象
        session = factory.openSession();
        //【5】SqlSession对象获取代理对象
        mapper = session.getMapper(UserMapper.class);
    }

    @After
    public void after() throws IOException {
        //释放资源
        session.close();
        is.close();
    }
}

这样Junit就会自动执行获取代理对象和释放资源的方法。

修改用户

  1. 持久层接口添加方法

    void update(User user);
    
  2. 映射文件添加标签

    <update id="update" parameterType="com.sxt.pojo.User">
        update user set username = #{username},sex=#{sex},address=#{address}
        where id = #{id}
    </update>
    
  3. 编写测试方法

    @Test
    public void testUpdate(){
        User user = new User(7,"程序员","女","山西");
        mapper.update(user);
        session.commit();
    }
    

3. MyBatis删除、根据Id查询

删除用户

  1. 持久层接口添加方法

    void delete(int userId);
    
  2. 映射文件添加标签

    <delete id="delete" parameterType="int">
        delete from user where id = #{id}
    </delete>
    

    注:当方法的参数类型是简单数据类型时,#{}中可以写任意名称

    • 简单数据类型:基本数据类型、字符串等
  3. 编写测试方法

    @Test
    public void testDelete(){
        mapper.delete(7);
        session.commit();
    }
    

根据ID查询用户

查询是不需要提交事务的,因为没有改变数据库

  1. 持久层接口添加方法

    User findById(int userId);
    
  2. 映射文件添加标签

    <select id="findById" resultType="com.sxt.pojo.User">
        select * from user where id = #{userId}
    </select>
    
  3. 编写测试方法

    @Test
    public void testFindByUserId(){
        User user = mapper.findById(6);
        System.out.println(user);
    }
    

4. MyBatis模糊查询

image-20220413144614941

4.1 使用#定义参数

  1. 持久层接口添加方法

    List<User> findByNameLike(String username);
    
  2. 映射文件添加标签

    <select id="findByUsernameLike" parameterType="string" resultType="com.sxt.pojo.User">
        select * from user where username like #{username}
    </select>
    
  3. 编写测试方法

    @Test
    public void testFindByUsernameLike(){
        List<User> list = mapper.findByUsernameLike("%尚学堂%");
        list.forEach(System.out::println);
    }
    

我们看到在映射文件中,parameterType的值为 string 而没有写java.lang.String ,这是为什么呢?

  • 参数/返回值类型为基本数据类型/包装类/String等类型时,我们可以写全类名,也可以写别名

image-20220413144745263

4.2 使用$定义参数

模糊查询如果不想在调用方法时参数加%,可以使用拼接参数的方式设置Sql:

<select id="findByUsernameLike" parameterType="string" resultType="com.sxt.pojo.User">
    select * from user where username like '%${value}%'
</select>

测试方法写法如下:

@Test
public void testFindByUsernameLike(){
    List<User> list = mapper.findByUsernameLike("尚学堂");
    list.forEach(System.out::println);
}

#和$的区别:

  1. #表示sql模板的占位符,$表示将字符串拼接到sql模板中。
  2. #可以防止sql注入,一般能用#就不用KaTeX parse error: Expected 'EOF', got '#' at position 2: 。#̲相当于是preparedSta…则是statement
  3. ${}内部的参数名必须写value。当parameterType 是唯一属性时 ${ } 可以写任意值的 但作为 见名知义 建议使用 实体的属性名称

4.3 使用<bind>定义参数

如果使用 # 还不想在调用方法的参数中添加 % ,可以使用 <bind> ,<bind> 允许我们在 Sql语句以外创建一个变量,并可以将其绑定到当前的Sql语句中。用法如下:

<select id="findByUsernameLike" parameterType="string" resultType="com.sxt.pojo.User">
    <bind name="likeName" value="'%'+username+'%'"/>
    select * from user where username like #{likeName}
</select>

测试方法写法如下:

@Test
public void testFindByUsernameLike(){
    List<User> list = mapper.findByUsernameLike("尚学堂");
    list.forEach(System.out::println);
}

5. MyBatis分页查询

image-20220413152320701

分页查询时,Sql语句使用limit关键字,需要传入开始索引和每页条数两个参数。MyBatis的多参数处理有以下方式:

5.1 顺序传参

Sql中的参数使用arg0,arg1…或param1,param2…表示参数的顺序。此方法可读性较低,在开发中不建议使用。注意只能是arg或param,别的会报错

  1. 持久层接口方法

    /**
         * 分页查询
         * @param startIndex 开始索引
         * @param pageSize 每页条数
         * @return
         */
    List<User> findPage(int startIndex,int pageSize);
    
  2. 映射文件

    <select id="findPage" resultType="com.sxt.pojo.User">
        select * from user limit #{arg0},#{arg1}
    </select>
    <!--或者:-->
    <select id="findPage" resultType="com.sxt.pojo.User">
        select * from user limit #{param1},#{param2}
    </select>
    
  3. 测试类

    @Test
    public void testFindPage(){
        List<User> list = mapper.findPage(0, 3);
        list.forEach(System.out::println);
    }
    

5.2 @Param传参

在接口方法的参数列表中通过==@Param定义参数名称==,在Sql语句中通过注解中所定义的参数名称指定参数位置。此方式参数比较直观的,推荐使用。

  1. 持久层接口方法

    List<User> findPage1(@Param("startIndex") int startIndex, @Param("pageSize") int pageSize);
    
  2. 映射文件

    <select id="findPage1" resultType="com.sxt.pojo.User">
        select * from user limit #{startIndex},#{pageSize}
    </select>
    
  3. 测试类

    @Test
    public void testFindPage1(){
        List<User> list = mapper.findPage1(0, 3);
        list.forEach(System.out::println);
    }
    

5.3 POJO传参

自定义POJO类,该类的属性就是要传递的参数,在SQL语句中绑定参数时使用POJO的属性名作为参数名即可。此方式推荐使用。

  1. 自定义POJO

    public class PageQuery {
        private int startIndex;
        private int pageSize;
        // 省略getter/setter/构造方法
    }
    
  2. 持久层接口方法

    List<User> findPage2(PageQuery pageQuery);
    
  3. 映射文件

    <select id="findPage2" resultType="com.sxt.pojo.User" parameterType="com.sxt.pojo.PageQuery">
        select * from user limit #{startIndex},#{pageSize}
    </select>
    
  4. 测试类

    @Test
    public void testFindPage2(){
        PageQuery pageQuery = new PageQuery(3,2);
        List<User> list = mapper.findPage2(pageQuery);
        list.forEach(System.out::println);
    }
    

5.4 Map传参

如果不想自定义POJO,可以使用Map作为传递参数的载体,在SQL语句中绑定参数时使用Map的Key作为参数名即可。此方法推荐使用。

  1. 持久层接口方法

    List<User> findPage3(Map<String,Object> params);
    
  2. 映射文件

    <select id="findPage3" resultType="com.sxt.pojo.User" parameterType="map">
        select * from user limit #{startIndex},#{pageSize}
    </select>
    
  3. 测试类

    @Test
    public void testFindPage3(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("startIndex",0);
        map.put("pageSize",4);
        List<User> list = mapper.findPage3(map);
        list.forEach(System.out::println);
    }
    

6. MyBatis聚合查询、主键回填

6.1 查询用户总数

  1. 持久层接口方法

    int findCount();
    
  2. 映射文件

    <select id="findCount" resultType="int">
        select count(id) from user
    </select>
    
  3. 测试类

    @Test
    public void testFindCount(){
        int count = mapper.findCount();
        System.out.println(count);
    }
    

6.2 主键回填

有时我们需要获取新插入数据的主键值。如果数据库中主键是自增的,这时我们就需要使用MyBatis的主键回填功能。

  1. 持久层接口方法

    void add2(User user);
    
  2. 映射文件

    <insert id="add2" parameterType="com.sxt.pojo.User">
        <!--keyProperty:主键属性名 keyColumn:主键列名 resultType:主键类型 order:执行时机-->
        <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
            select last_insert_id();
        </selectKey>
        insert into user(username,sex,address)
        values(#{username},#{sex},#{address})
    </insert>
    

    SELECT LAST_INSERT_ID():查询刚刚插入的记录的主键值,只适用于自增主键,且必须和insert语句一起执行。

  3. 测试类

    @Test
    public void testAdd2(){
        User user = new User("程序员", "男", "天津");
        System.out.println(user);
        mapper.add2(user);
        session.commit();
        System.out.println(user);
    }
    

四、MyBatis配置文件

image-20220413165134177

1. properties标签

MyBatis配置文件结构:

-configuration
    -properties(属性)
        -property
    -settings(全局配置参数)
        -setting
    -plugins(插件)
        -plugin
    -typeAliases(别名)
        -typeAliase
        -package
    -environments(环境)
        -environment
            -transactionManager(事务管理)
            -dataSource(数据源)
    -mappers(映射器)
        -mapper
        -package

properties

属性值定义。properties标签中可以定义属性值,也可以引入外部配置文件。无论是内部定义还是外部引入,都可以使用==${name}获取值==。

例如:我们可以将数据源配置写到外部的db.properties中,再使用properties标签引入外部配置文件,这样可以做到动态配置数据源

  1. 编写db.properties

    jdbc.driver = com.mysql.jdbc.Driver
    jdbc.url = jdbc:mysql:///mybatis
    jdbc.username = root
    jdbc.password = 123456
    
  2. 在配置文件中引入db.properties

    <!--动态配置数据源-->
    <properties resource="db.properties"></properties>
    <!--配置环境-->
    <environments default="mysql">
        <environment id="mysql">
            <!--事务类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--数据源-->
            <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>
    

    当然我们也可以将数据源数据通过 <properties> 配置到MyBatis配置文件内,但这样做没什么意义。

    <properties>
        <property name="jdbc" value="com.mysql.jdbc.Driver"/>
        <property name="jdbc" value="jdbc.url"/>
        <property name="jdbc" value="jdbc.username"/>
        <property name="jdbc" value="jdbc.password"/>
    </properties>
    <!--配置环境-->
    <environments default="mysql">
        <environment id="mysql">
            <!--事务类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--数据源-->
            <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>
    

2. settings标签

<settings> 是配置MyBatis运行时的一些行为的,例如缓存、延迟加载、命名规则等一系列控制性参数。后期我们会使用该标签配置缓存和延迟加载等。

3. plugins标签

<plugins> 是配置MyBatis插件的。插件可以增强MyBatis功能,比如进行sql增强,打印日志,异常处理等。后期我们会使用该标签配置分页插件

4. typeAliases标签

f70fa76f3383dc83eaabef6a53d7eb93

MyBatis对常用类有默认别名支持,比如java.lang.Stirng的别名为string。除此之外,我们也可以使用 <typeAliases> 设置自定义别名。

4.1 为一个类配置别名

<typeAliases>
    <typeAlias type="全类名" alias="别名"></typeAlias>
</typeAliases>

此时我们即可在映射文件中使用自定义别名,如:

  1. 配置文件:

    <typeAliases>
        <!--type:全类名,alias:别名-->
        <typeAlias type="com.sxt.pojo.User" alias="user"></typeAlias>
    </typeAliases>
    
  2. 映射文件:

    别名小写

    <select id="findAll" resultType="user">
        select * from user
    </select>
    

4.2 为一个所有包下的所有类配置别名

<typeAliases>
    <package name="包名"></package>
</typeAliases>

此时该包下的所有类都有了别名,别名省略包名,和类名相同。
如:

  1. 配置文件:

    <typeAliases>
        <!--为该包下的所有类配置别名,别名省略包名,和类名相同-->
        <package name="com.sxt.pojo"/>
    </typeAliases>
    
  2. 映射文件:

    注意:和类名相同,首字母大写

    <select id="findAll" resultType="User">
        select * from user
    </select>
    
    <select id="findPage2" resultType="User" parameterType="PageQuery">
        select * from user limit #{startIndex},#{pageSize}
    </select>
    

5. environments标签

image-20220414083738190

<environments> 可以为MyBatis配置数据环境。

5.1 事务管理

不推荐使用MANAGED

<environments default="mysql">
        <environment id="mysql">
            <!--JDBC:使用JDBC的提交和回滚 MANAGED:不做事务处理-->
            <transactionManager type="JDBC"></transactionManager>
    </environment>
</environments>

5.2 连接池

<environments default="mysql">
    <environment id="mysql">
        <transactionManager type="JDBC"></transactionManager>
        <!-- 连接池设置 -->
        <dataSource type="POOLED">
            <!-- 数据源设置... -->
        </dataSource>
    </environment>
</environments>

dataSource的type属性:

  • POOLED:使用连接池管理连接,使用MyBatis自带的连接池
  • UNPOOLED:不使用连接池,直接由JDBC连接。
  • JNDI:由JAVAEE服务器管理连接,如果使用Tomcat作为服务器则使用Tomcat自带的连接池管理。

6. mappers标签

image-20220414083913579

<mappers> 用于注册映射文件或持久层接口,只有注册的映射文件才能使用,共有四种方式都可以完成注册:

  1. 使用相对路径注册映射文件

    <mappers>
        <!--相对路径:使用resource-->
        <mapper resource="com/sxt/mapper/UserMapper.xml"></mapper>
    </mappers>
    
  2. 使用绝对路径注册映射文件

    注意:要在url加上file:///

    <mappers>
        <!--绝对路径:使用url,注意要在路径前面加上file:///-->
        <mapper url="file:///C:\JavaStudy\IdeaProjects\mybatiscase\mybatisDemo1\src\main\resources\com\sxt\mapper\UserMapper.xml"></mapper>
    </mappers>
    
  3. 注册持久层接口

    <mappers>   
        <!--注册持久层接口-->
        <mapper class="com.sxt.mapper.UserMapper"></mapper>
    </mappers>
    
  4. 注册一个包下的所有持久层接口

    <mappers>
        <!--注册一个包下的所有持久层接口-->
        <package name="com.sxt.mapper"/>
    </mappers>
    

五、MyBatis映射文件

1. resultMap标签自定义映射关系

image-20220414091408171

MyBatis映射文件中除了 <insert> 、 <delete> 、 <update> 、 <select> 外,还有一些标签可以使用:

resultMap

标签的作用的自定义映射关系。

MyBatis可以将数据库结果集封装到对象中,是因为结果集的列名和对象属性名相同

image-20220414091529450

当POJO属性名和数据库列名不一致时,MyBatis无法自动完成映射关系。如:

image-20220414091547839

使用idea自带的连接数据库:

image-20220414093128973

此时有两种解决方案:

  1. Sql语句的查询字段起与POJO属性相同的别名

    在select语句中使用as起别名,和teacher对象中字段名相同的别名

    <select id="findAll" resultType="com.sxt.pojo.Teacher">
        select tid as id,tname as teacherName from teacher;
    </select>
    
  2. 自定义映射关系

    • 在映射文件中,使用 <resultMap> 自定义映射关系:

      id定义主键列 property:POJO属性名 column:数据库列名

      result定义普通列 property:POJO属性名 column:数据库列名

      <!--id:自定义映射名 type:自定义映射的对象类型-->
      <resultMap id="teacherMapper" type="com.sxt.pojo.Teacher">
          <!--id定义主键列 property:POJO属性名 column:数据库列名-->
          <id property="id" column="tid"></id>
          <!--result定义普通列  property:POJO属性名 column:数据库列名-->
          <result property="teacherName" column="tname"></result>
      </resultMap>
      
    • 在 <select> 标签中,使用 resultMap 属性代替 resultType 属性,使用自定义映射关系。

      <select id="findAll" resultMap="teacherMapper">
          select * from teacher;
      </select>
      

2. sql、include标签重用sql

image-20220414091702500

<sql> 用来定义可重用的Sql片段,通过 <include> 引入该片段。如:Sql语句的查询字段起与POJO属性相同的别名,该Sql片段就可以重用。

<sql id="selectAllField">
    select tid as id,tname as teacherName
</sql>

<select id="findAll" resultType="com.sxt.pojo.Teacher">
    <include refid="selectAllField"></include>
    from teacher;
</select>

<select id="findById" resultType="com.sxt.pojo.Teacher">
    <include refid="selectAllField"></include>
    from teacher
    where tid = #{id}
</select>

3. 特殊字符处理

image-20220414091738228

在Mybatis映射文件中尽量不要使用一些特殊字符,如: < , > 等。我们可以使用符号的实体来表示:

image-20220414091751613

如:

<select id="findById2" resultType="com.sxt.pojo.Teacher">
    <include refid="selectAllField"></include>
    from Teacher
    where tid &gt; #{id}
</select>

六、动态SQL

1. if标签

image-20220414103103777

一个查询的方法的Sql语句不一定是固定的。比如电商网站的查询商品,用户使用不同条件查询,Sql语句就会添加不同的查询条件。此时就需要在方法中使用动态Sql语句。

<if>

<if> 标签内的Sql片段在满足条件后才会添加,用法为: <if test=“条件”> 。例如:根据不同条件查询用户:

  1. 持久层接口添加方法

    // 用户通用查询
    List<User> findByCondition(User user);
    
  2. 映射文件添加标签

    <select id="findByCondition" parameterType="com.sxt.pojo.User" resultType="com.sxt.pojo.User">
        select * from user where 1 = 1
        <if test="username!=null and username.length()!=0">
            and username like #{username}
        </if>
        <if test="sex!=null and sex.length()!=0">
            and sex = #{sex}
        </if>
        <if test="address!=null and address.length()!=0">
            and address = #{address}
        </if>
    </select>
    
  3. 编写测试方法

    @Test
    public void testFindByCondition(){
        User user = new User();
        //        List<User> list = mapper.findByCondition(user);
        //        list.forEach(System.out::println);
    
        user.setUsername("%尚学堂%");
        //        List<User> list1 = mapper.findByCondition(user);
        //        list1.forEach(System.out::println);
    
        user.setUsername("%尚学堂%");
        user.setAddress("北京");
        List<User> list2 = mapper.findByCondition(user);
        list2.forEach(System.out::println);
    }
    
  1. if中的条件不能使用&&/||,而应该使用and/or
  2. if中的条件可以直接通过属性名获取参数POJO的属性值,并且该值可以调用方法
  3. where后为什么要加1=1?
    任意条件都可能拼接到Sql中。如果有多个条件,从第二个条件开始前都需要加And关键字。加上1=1这个永久成立的条件,就不需要考虑后面的条件哪个是第一个条件,后面的条件前都加And关键字即可。

2. where标签

image-20220414105516310

<where> 可以代替sql中的where 1=1 和第一个and,更符合程序员的开发习惯,使用 <where> 后的映射文件如下:

<select id="findByCondition" parameterType="com.sxt.pojo.User" resultType="com.sxt.pojo.User">
    select * from user
    <where>
        <if test="username!=null and username.length()!=0">
            username like #{username}
        </if>
        <if test="sex!=null and sex.length()!=0">
            and sex = #{sex}
        </if>
        <if test="address!=null and address.length()!=0">
            and address = #{address}
        </if>
    </where>
</select>

3. set标签

<set> 标签用在update语句中。借助 <if> ,可以只对有具体值的字段进行更新。 <set> 会自动添加set关键字,并去掉最后一个if语句中多余的逗号

<update id="updateUser" parameterType="com.sxt.pojo.User">
    update user
    <set>
        <if test="username !=null and username.length()!=0">
            username = #{username},
        </if>
        <if test="sex!=null and sex.length()!=0">
            sex = #{sex},
        </if>
        <if test="address!=null and address.length()!=0">
            addresss = #{address}
        </if>
    </set>
    <where>
        id = #{id}
    </where>
</update>

4. choose、when、otherwise标签

image-20220414113033669

这些标签表示多条件分支,类似JAVA中的 switch…case 。 <choose> 类似switch , <when> 类似 case , <otherwise> 类似 default ,用法如下:

<select id="findByUsername" resultType="com.sxt.pojo.User" parameterType="com.sxt.pojo.User">
    select * from user
    <where>
        <choose>
            <when test="username.length() &lt; 5">
                <bind name="likeName" value="'%'+username+'%'"/>
                username like #{likeName}
            </when>
            <when test="username.length() &lt; 10">
                username like #{userName}
            </when>
            <otherwise>
                id = 1
            </otherwise>
        </choose>
    </where>
</select>

这段代码的含义为:用户名<5时使用模糊查询,用户名>=5并且<10时使用精确查询,否则查询id为1的用户

5. foreach标签

ceeb653ely1g0r3ywpaajg207s057wlr

<foreach> 类似JAVA中的for循环,可以遍历集合或数组。 <foreach> 有如下属性:

  • collection:遍历的对象类型
  • open:开始的sql语句
  • close:结束的sql语句
  • separator:遍历每项间的分隔符
  • item:表示本次遍历获取的元素,遍历List、Set、数组时表示每项元素,遍历map时表示键值对的值。
  • index:遍历List、数组时表示遍历的索引,遍历map时表示键值对的键。

5.1 遍历数组

我们使用 <foreach> 遍历数组进行批量删除

  1. 持久层接口添加方法

    //批量删除 delete from user where id in( , , )
    void deleteBatch(int[] ids);
    
  2. 映射文件添加标签

    <delete id="deleteBatch" parameterType="int">
        delete from user
        <where>
            <foreach open="id in(" close=")" collection="array" item="id" separator="," >
                #{id}
            </foreach>
        </where>
    </delete>
    
  3. 编写测试方法

    @Test
    public void testDeleteBatch(){
        int[] ids = {9,10};
        mapper.deleteBatch(ids);
        session.commit();
    }
    

5.2 遍历Collection

<foreach> 遍历List和Set的方法是一样的,我们使用 <foreach> 遍历List进行批量添加

  1. 持久层接口添加方法

    //批量新增 insert into user(username,sex,address) values(),(),()
    void insertBatch(List<User> user);
    
  2. 映射文件添加标签

    <insert id="insertBatch" parameterType="com.sxt.pojo.User">
        insert into user(username,sex,address) values
        <foreach collection="list" item="user" separator=",">
            (#{user.username},#{user.sex},#{user.address})
        </foreach>
    </insert>
    
  3. 编写测试方法

    @Test
    public void testInsertBatch(){
        User user = new User("程序猿1", "女", "北京");
        User user1 = new User("程序猿2", "男", "天津");
        List<User> list = new ArrayList<>();
        list.add(user);
        list.add(user1);
        mapper.insertBatch(list);
        session.commit();
    }
    

5.3 遍历Map

我们使用 <foreach> 遍历Map进行多条件查询

  1. 持久层接口添加方法

    遍历map需要设置参数名

    /**
         * 多条件查询
         * @param map   查询的条件键值对 键:属性名 值:属性值
         *              select * from user where username = ? and sex = ?
         * @return
         */
    List<User> findUser(@Param("queryMap") Map<String,Object> map);
    
  2. 映射文件添加标签

    index代表键,item代表值

    key需要使用$拼接,value使用#占位符

    属性名不能是占位符,必须是拼接进来的,属性值可以是占位符

    <select id="findUser" parameterType="map" resultType="com.sxt.pojo.User">
        select * from user
        <where>
            <foreach collection="queryMap" separator="and" index="key" item="value">
                ${key} = #{value}
            </foreach>
        </where>
    </select>
    
  3. 编写测试方法

    @Test
    public void testFindUser(){
        Map<String,Object> map = new HashMap<>();
        map.put("sex","男");
        map.put("address","北京");
        List<User> list = mapper.findUser(map);
        list.forEach(System.out::println);
    }
    

七、MyBatis缓存

1. 缓存介绍

image-20220414144345034

缓存是内存当中一块存储数据的区域,目的是提高查询效率。MyBatis会将查询结果存储在缓存当中,当下次执行相同的SQL时不访问数据库,而是直接从缓存中获取结果,从而减少服务器的压力。

  • 什么是缓存?

    存在于内存中的一块数据。

  • 缓存有什么作用?

    减少程序和数据库的交互,提高查询效率,降低服务器和数据库的压力。

  • 什么样的数据使用缓存?

    经常查询但不常改变的,改变后对结果影响不大的数据。

  • MyBatis缓存分为哪几类?

    一级缓存和二级缓存

  • 如何判断两次Sql是相同的?

    1. 查询的Sql语句相同
    2. 传递的参数值相同
    3. 对结果集的要求相同
    4. 预编译的模板Id相同

2. MyBatis一级缓存

image-20220414144536345

  • MyBatis一级缓存也叫本地缓存SqlSession对象中包含一个Executor对象,Executor对象中包含一个PerpetualCache对象,在该对象存放一级缓存数据。
  • 由于一级缓存是在SqlSession对象中,所以只有使用同一个SqlSession对象操作数据库时才能共享一级缓存。
  • MyBatis的一级缓存是默认开启的,不需要任何的配置。

测试一级缓存

第一个findById查询数据库

第二个findById从缓存中拿结果

同一个sqlSession对象可以共享

image-20220414153919042

两个sqlSession对象不能共享

image-20220414155841787

@Test
public void testCache1() throws IOException {
    //【1】读取mybatis核心配置文件
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
    //【2】创建构建者对象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //【3】创建工厂
    SqlSessionFactory factory = builder.build(is);
    //【4】获取SqlSession对象
    SqlSession sqlSession = factory.openSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);

    User user = mapper.findById(1);
    System.out.println(user);
    System.out.println("-----------------------------------");
    User user1 = mapper1.findById(1);
    System.out.println(user1);
}

@Test
public void testCache2() throws IOException {
    //【1】读取mybatis核心配置文件
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
    //【2】创建构建者对象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //【3】创建工厂
    SqlSessionFactory factory = builder.build(is);
    //【4】获取SqlSession对象
    SqlSession sqlSession1 = factory.openSession();
    SqlSession sqlSession2 = factory.openSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

    User user = mapper1.findById(1);
    System.out.println(user);
    System.out.println("-----------------------------------");
    User user1 = mapper2.findById(1);
    System.out.println(user1);
}

3. MyBatis清空一级缓存

image-20220414144625537

进行以下操作可以清空MyBatis一级缓存:

  • SqlSession 调用 close() :操作后SqlSession对象不可用,该对象的缓存数据也不可用。
  • SqlSession 调用 clearCache() / commit() :操作会清空一级缓存数据。
  • SqlSession 调用增删改方法:操作会清空一级缓存数据,因为增删改后数据库发生改变,缓存数据将不准确。
@Test
public void testCache3() throws IOException {
    //【1】读取mybatis核心配置文件
    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
    //【2】创建构建者对象
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //【3】创建工厂
    SqlSessionFactory factory = builder.build(is);
    //【4】获取SqlSession对象
    SqlSession sqlSession = factory.openSession();

    UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession.getMapper(UserMapper.class);

    User user = mapper1.findById(1);
    System.out.println(user);
    //清空方法一:
    //        sqlSession.close();
    //清空方法二:
    //        sqlSession.clearCache();
    //        sqlSession.commit();
    //清空方法三:
    mapper1.delete(2);
    System.out.println("-----------------------------------");
    User user1 = mapper2.findById(1);
    System.out.println(user1);
}

4. MyBatis二级缓存

image-20220414144712617

  • MyBatis二级缓存也叫全局缓存。数据存放在SqlSessionFactory中,只要是同一个工厂对象创建的SqlSession,在进行查询时都能共享数据。一般在项目中只有一个SqlSessionFactory对象,所以二级缓存的数据是全项目共享的。

  • MyBatis一级缓存存放的是对象二级缓存存放的是对象的数据。所以要求二级缓存存放的POJO必须是可序列化的,也就是要实现Serializable接口。

  • MyBatis二级缓存默认不开启,手动开启后数据先存放在一级缓存中,只有一级缓存数据清空后,数据才会存到二级缓存中

    SqlSession 调用 clearCache() 无法将数据存到二级缓存中。

开启二级缓存

  1. POJO类实现Serializable接口。

    public class User implements Serializable 
    {
        private int id;
        private String username;
        private String sex;
        private String address;
    }
    
  2. 在MyBatis核心配置文件添加如下设置:

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

    由于cacheEnabled默认值是true,所以该设置可以省略。

  3. 在映射文件添加 <cache /> 标签,该映射文件下的所有方法都支持二级缓存。

    如果不加cache标签,则无法使用二级缓存

    如果查询到的集合中对象过多,二级缓存只能缓存1024个对象引用。可以通过 <cache /> 标签的size属性修改该数量。

    <cache size="2048"/>
    
  4. 测试二级缓存

    image-20220414162437413

    @Test
    public void testCache4() throws IOException {
        //【1】读取mybatis核心配置文件
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //【2】创建构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //【3】创建工厂
        SqlSessionFactory factory = builder.build(is);
        //【4】获取SqlSession对象
        //这两个sqlSession是同一个工厂创造的,是可以共享二级缓存的
        SqlSession sqlSession1 = factory.openSession();
        SqlSession sqlSession2 = factory.openSession();
    
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    
        User user = mapper1.findById(1);
        System.out.println(user);
        //让一级缓存失效
        sqlSession1.close();
        System.out.println("-----------------------------------");
        User user1 = mapper2.findById(1);
        System.out.println(user1);
    }
    

八、MyBatis关联查询

image-20220414162556621

MyBatis的关联查询分为一对一关联查询和一对多关联查询。

  • 查询对象时,将关联的另一个对象查询出来,就是一对一关联查询。
  • 查询对象时,将关联的另一个对象的集合查询出来,就是一对多关联查询。

例如有学生类和班级类:
一个学生对应一个班级,也就是学生类中有一个班级属性,这就是一对一关系。

一个班级对应多个学生,也就是班级类中有一个学生集合属性,这就是一对多关系。

实体类设计如下:

public class Student {
    private int sid;
    private String name;
    private int age;
    private String sex;
    //一
    private Classes classes;
    // 省略getter/setter/toString
}
public class Classes {
    private int cid;
    private String className;
    //多
    private List<Student> studentList;
    // 省略getter/setter/toString
}

数据库设计如下:

在多方表设置一方的外键

image-20220414162701936

1. MyBatis一对一关联查询

image-20220414163400926

查询学生时,将关联的一个班级对象查询出来,就是一对一关联查询

1.1 创建持久层接口

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

1.2 创建映射文件

<association>关联对象列

一对一对象列

property:属性名 column:关联列名 javaType:对象类型

<resultMap id="studentMapper" type="com.sxt.pojo.Student">
    <!--主键列-->
    <id property="sid" column="sid"></id>
    <!--普通列-->
    <result property="name" column="name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <!--一对一对象列 property:属性名 column:关联列名 javaType:对象类型-->
    <!--班级对象-->
    <association property="classes" column="classId" javaType="com.sxt.pojo.Classes">
        <!--关联对象主键列-->
        <id property="cid" column="cid"></id>
        <!--关联对象普通列-->
        <result property="className" column="className"></result>
    </association>
</resultMap>
<select id="findAll" resultMap="studentMapper">
    select * from student left join classes on student.classId = classes.cid;
</select>

1.3 配置文件注册映射文件

<!--注册映射文件-->
<mappers>
    <package name="com.sxt.mapper"/>
</mappers>

1.4 测试一对一关联查询

@Test
public void testFindAllStudent(){
    StudentMapper mapper = session.getMapper(StudentMapper.class);
    List<Student> list = mapper.findAll();
    list.forEach(System.out::println);
}

image-20220414181159478

2. MyBatis一对多关联查询

image-20220414181327610

查询班级时,将关联的学生集合查询出来,就是一对多关联查询

2.1 创建持久层接口

public interface ClassesMapper {
    List<Classes> findAll();
}

2.2 创建映射文件

<collection>集合列

property:属性名 column:关联列 ofType:集合的泛型

<resultMap id="classesMapper" type="com.sxt.pojo.Classes">
    <id property="cid" column="cid"></id>
    <result property="className" column="className"></result>
    <!--集合列 property:属性名 column:关联列 ofType:集合的泛型-->
    <!--学生集合-->
    <collection property="students" column="classId" ofType="com.sxt.pojo.Student">
        <id property="sid" column="sid"></id>
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
    </collection>
</resultMap>
<select id="findAll" resultMap="classesMapper">
    select * from classes left join student on classes.cid = student.classId;
</select>

2.3 测试一对多关联查询

@Test
public void testFindAllClasses(){
    ClassesMapper mapper = session.getMapper(ClassesMapper.class);
    List<Classes> list = mapper.findAll();
    list.forEach(System.out::println);
}

image-20220415074930120

3. MyBatis多对多关联查询

image-20220414181536954

MyBatis多对多关联查询本质就是两个一对多关联查询

例如有老师类和班级类:

一个老师对应多个班级,也就是老师类中有一个班级集合属性。

一个班级对应多个老师,也就是班级类中有一个老师集合属性。

实体类设计如下:

public class Teacher {
    private Integer tid;
    private String tname;
    private List<Classes> classes;
    // 省略getter/setter/toString
}
public class Classes {
    private Integer cid;
    private String className;
    private List<Student> studentList;
    private List<Teacher> teacherList;
    // 省略getter/setter/toString
}

在数据库设计中,需要建立中间表双方与中间表均为一对多关系

image-20220414181616549

接下来测试查询老师时,将关联的班级集合查询出来

3.1 创建持久层接口

public interface TeacherMapper {
    List<Teacher> findAll();
}

3.2 创建映射文件

<!--封装老师-->
<resultMap id="TeacherMapper" type="com.sxt.pojo.Teacher">
    <id property="tid" column="tid"></id>
    <result property="tname" column="tname"></result>
    <!--班级集合 column:关联列-->
    <collection property="classes" column="tid" ofType="com.sxt.pojo.Classes">
        <id column="cid" property="cid"></id>
        <result column="className" property="className"></result>
    </collection>
</resultMap>
<select id="findAll" resultMap="TeacherMapper">
    select *
    from teacher
    	left join classes_teacher
    		on teacher.tid = classes_teacher.tid
    	left join classes
    		on classes_teacher.cid = classes.cid
</select>

3.3 测试多对多关联查询

@Test
public void testFindAllTeacher(){
    TeacherMapper mapper = session.getMapper(TeacherMapper.class);
    List<Teacher> list = mapper.findAll();
    list.forEach(System.out::println);
}

image-20220415084831795

如果想查询班级时,将关联的老师集合查询出来,只需要修改班级映射文件的Sql语句和 <resultMap> 即可:

<resultMap id="classesMapper" type="com.sxt.pojo.Classes">
    <id property="cid" column="cid"></id>
    <result property="className" column="className"></result>
    <!--集合列 property:属性名 column:关联列 ofType:集合的泛型-->
	<!--学生-->
    <collection property="students" column="classId" ofType="com.sxt.pojo.Student">
        <id property="sid" column="sid"></id>
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
    </collection>
    <!--老师-->
    <collection property="teachers" column="cid" ofType="com.sxt.pojo.Teacher">
        <id property="tid" column="tid"></id>
        <result property="tname" column="tname"></result>
    </collection>
</resultMap>
<select id="findAll" resultMap="classesMapper">
    select *
    from classes
    	left join student
    		on classes.cid = student.classId
    	left join classes_teacher
    		on classes.cid = classes_teacher.cid
    	left join teacher
    		on teacher.tid = classes_teacher.tid
</select>

4. MyBatis分解式查询_一对多

image-20220414181726871

在MyBatis多表查询中,使用连接查询时一个Sql语句就可以查询出所有的数据。如:

# 查询班级时关联查询出学生
select *
from classes
    left join student
    on student.classId = classes.cid

也可以使用分解式查询,即将一个连接Sql语句分解为多条Sql语句,如:

# 查询班级时关联查询出学生
select * from classes;

select * from student where classId = 1;
select * from student where classId = 2;  

这种写法也叫N+1查询

连接查询:

  • 优点:降低查询次数,从而提高查询效率。
  • 缺点:如果查询返回的结果集较多会消耗内存空间。

N+1查询:

  • 优点:结果集分步获取,节省内存空间。
  • 缺点:由于需要执行多次查询,相比连接查询效率低。

我们以查询班级时关联查询出学生为例,使用N+1查询:

4.1 创建每个查询语句的持久层方法

public interface ClassesMapper {
    // 查询所有班级
    List<Classes> findAll();
}
public interface StudentMapper {
    // 根据班级Id查询学生
    List<Student> findByClassId(int classId);
}

4.2 在映射文件中进行配置

在ClassMapper2中定义findAll方法

在StudentMapper2中定义findByClassId方法

<select id="findAll" resultType="com.sxt.pojo.Classes">
    select * from clasees
</select>

<select id="findByClassId" resultType="com.sxt.pojo.Student" parameterType="int">
    select * from student where classId = #{classId}
</select>

4.3 修改主表映射文件中的查询方法

调用从表的查询方法,通过调用学生的findByClassId方法查询所有班级的学生信息

在collection标签中添加select属性,通过这个select属性来调用findByClassId方法

<!--自定义映射关系-->
<resultMap id="myClassesMapper" type="com.sxt.pojo.Classes">
    <id property="cid" column="cid"></id>
    <result property="className" column="className"></result>
    <!--property:属性名,ofType:泛型名,select:从表查询调用的方法,column:查询传入的字段-->
    <collection property="students"
                ofType="com.sxt.pojo.Student"
                select="com.sxt.mapper2.StudentMapper2.findByClassId"
                column="cid">
    </collection>
</resultMap>
<select id="findAll" resultMap="myClassesMapper">
    select * from classes
</select>

4.4 测试查询方法

@Test
public void testFindAllClasses2(){
    ClassesMapper2 mapper2 = session.getMapper(ClassesMapper2.class);
    List<Classes> list = mapper2.findAll();
    list.forEach(System.out::println);
}

5. MyBatis分解式查询_一对一

查询学生时关联查询出班级也可以使用分解式查询,首先将查询语句分开:

select * from student;
select * from classes where cid = ?;

5.1 创建每个查询语句的持久层方法

//查询所有学生
List<Student> findAll();

//根据id查询班级
Classes findByCid(int cid);

5.2 在映射文件中进行配置

<select id="findByCid" resultType="com.sxt.pojo.Classes" parameterType="int">
    select * from classes where cid = #{cid}
</select>

<select id="findAll" resultMap="myStudentMapper">
    select * from student
</select>

5.3 修改主表映射文件中的查询方法

<resultMap id="myStudentMapper" type="com.sxt.pojo.Student">
    <id property="sid" column="sid"></id>
    <result property="name" column="name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <association property="classes"
                 javaType="com.sxt.pojo.Classes" select="com.sxt.mapper2.ClassesMapper2.findByCid"
                 column="classId">
    </association>
</resultMap>
<select id="findAll" resultMap="myStudentMapper">
    select * from student
</select>

5.4 测试查询方法

@Test
public void testFindAllStudent2(){
    StudentMapper2 mapper2 = session.getMapper(StudentMapper2.class);
    List<Student> list = mapper2.findAll();
    list.forEach(System.out::println);
}

6. MyBatis延迟加载

image-20220414182109578

==分解式查询==又分为两种加载方式:

  • 立即加载:在查询主表时就执行所有的Sql语句。
  • 延迟加载:又叫懒加载,首先执行主表的查询语句,使用从表数据时才触发从表的查询语句。

延迟加载在获取关联数据时速度较慢,但可以节约资源,即用即取

6.1 开启延迟加载

  • 设置所有的N+1查询都为延迟加载:

    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    
  • 设置某个方法为延迟加载:

    在 <association> 、 <collection> 中添加fetchType属性设置加载方式。lazy:延迟加载;eager:立即加载。

6.2 测试延迟加载

先查询出所有班级,学生集合延迟加载,只有用到学生集合的时候才去加载

@Test
public void testFindAllClasses2(){
    ClassesMapper2 mapper2 = session.getMapper(ClassesMapper2.class);
    List<Classes> list = mapper2.findAll();
    list.forEach(System.out::println);
    System.out.println("---------------------");
    System.out.println(list.get(0).getStudents());
}

由于打印对象时会调用对象的 toString 方法, toString 方法默认会触发延迟加载的查询,所以我们无法测试出延迟加载的效果。

我们在配置文件设置lazyLoadTriggerMethods属性,该属性指定对象的什么方法触发延迟加载,设置为空字符串即可。

<!--任何方法都触发不了延迟加载-->
<settings>
    <setting name="lazyLoadTriggerMethods" value=""/>
</settings>

一般情况下,一对多查询使用延迟加载,一对一查询使用立即加载

image-20220415111057054

九、MyBatis注解开发

1. 环境搭建

image-20220415115624896

MyBatis可以使用注解替代映射文件。映射文件的作用就是定义Sql语句,可以在持久层接口上使用

@Select/@Delete/@Insert/@Update定义Sql语句,这样就不需要使用映射文件了。

  1. 创建maven工程,引入依赖

  2. 创建mybatis核心配置文件SqlMapConfig.xml

  3. 将log4j.properties文件放入resources中,让控制台打印SQL语句。

  4. 创建实体类

  5. 创建持久层接口,并在接口方法上定义Sql语句

    public interface UserMapper {
        @Select("select * from user")
        List<User> findAll();
    }
    

    由于注解在方法上方,而方法中就有参数类型和返回值类型,所以使用注解开发不需要定义参数类型和返回值类型

  6. 在核心配置文件注册持久层接口,由于没有映射文件,所以只能采用注册接口或注册包的方法。

    <mappers>
        <package name="com.itbaizhan.mapper"/>
    </mappers>
    
  7. 测试方法

    @Test
    public void testFindAll(){
        UserMapper mapper = session.getMapper(UserMapper.class);
        List<User> list = mapper.findAll();
        list.forEach(System.out::println);
    }
    

2. 增删改查

image-20220415115904038

接下来写一套基于MyBatis注解的增删改查方法:

public interface UserMapper {
    @Select("select * from user")
    List<User> findAll();

    //主键回填:
    @SelectKey(keyColumn = "id",keyProperty = "id",resultType = int.class,before = false,statement = "select last_insert_id();")
    @Insert("insert into user(username,sex,address) values(#{username},#{sex},#{address})")
    void add(User user);

    @Update("update user set username = #{username},sex = #{sex},address = #{address} where id = #{id}")
    void update(User user);

    @Delete("delete from user where id = #{id}")
    void delete(int id);

    @Select("select * from user where username like #{username}")
    List<User> findByUsernameLike(String username);
}

3. 动态sql

image-20220415140956568

MyBatis注解开发中有两种方式构建动态Sql:

3.1 使用脚本标签

将Sql嵌套在 <script> 内即可使用动态Sql标签:

//根据任意条件查询
@Select("<script>" +
        " select * from user\n" +
        "        <where>\n" +
        "            <if test=\"username!=null and username.length()!=0\">\n" +
        "                username like #{username}\n" +
        "            </if>\n" +
        "            <if test=\"sex!=null and sex.length()!=0\">\n" +
        "                and sex = #{sex}\n" +
        "            </if>\n" +
        "            <if test=\"address!=null and address.length()!=0\">\n" +
        "                and address = #{address}\n" +
        "            </if>\n" +
        "        </where> " +
        "</script>")
List<User> findByCondition(User user);

3.2 在方法中构建动态Sql

在MyBatis中有 @SelectProvider 、 @UpdateProvider 、 @DeleteProvider 、@InsertProvider 注解。当使用这些注解时将不在注解中直接编写SQL而是调用某个类的方法来生成SQL

public class UserProvider {
    //生成根据任意条件查询的sql语句
    public String findByConditionSql(User user){
        StringBuffer sb = new StringBuffer("select * from user where 1=1");
        if(user.getUsername()!=null && user.getUsername().length()!=0){
            sb.append(" and username like #{username}");
        }
        if(user.getSex()!=null && user.getSex().length()!=0){
            sb.append(" and sex = #{sex}");
        }
        if(user.getAddress()!=null && user.getAddress().length()!=0){
            sb.append(" and address = #{address}");
        }
        return sb.toString();
    }
}

UserMapper.java接口:

@SelectProvider(type = UserProvider.class,method = "findByConditionSql")
List<User> findByCondition(User user);
image-20220415143243842

4. 自定义映射关系

当POJO属性名与数据库列名不一致时,需要自定义实体类和结果集的映射关系,在MyBatis注解开发中,使用 @Results 定义并使用自定义映射,使用 @ResultMap 使用自定义映射,用法如下:

property:pojo属性名

column:数据库列名

//自定义映射
@Results(id = "userMapper",value = {
    @Result(id = true,property = "id",column = "id"),
    @Result(property = "username",column = "username1"),
    @Result(property = "sex",column = "sex1"),
    @Result(property = "address",column = "address1")
})
@Select("select * from user")
List<User> findAll();

//使用自定义映射
@ResultMap("userMapper")
@Select("select * from user where id = #{id}")
User findById(int id);

5. 二级缓存

image-20220415144610754

MyBatis默认开启一级缓存,接下来我们学习如何在注解开发时使用二级缓存:

  1. POJO类实现Serializable接口。

  2. 在MyBatis配置文件添加如下设置:

    该配置默认是true,可以不加

    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
  3. 在持久层接口上方加注解@CacheNamespace(blocking=true),该接口的所有方法都支持二级缓存。

  4. 测试二级缓存

    @Test
    public void testCache() throws IOException {
        //【1】读取核心配置文件
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //【2】创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //【3】SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
        SqlSessionFactory factory = builder.build(is);
        //【4】SqlSessionFactory对象获取SqlSession对象
        SqlSession session1 = factory.openSession();
        SqlSession session2 = factory.openSession();
    
        UserMapper mapper1 = session1.getMapper(UserMapper.class);
        User user1 = mapper1.findById(1);
        System.out.println(user1);
        System.out.println(user1.hashCode());
        session1.commit();//清空一级缓存,将数据存到二级缓存
    
        UserMapper mapper2 = session2.getMapper(UserMapper.class);
        User user2 = mapper2.findById(1);
        System.out.println(user2);
        System.out.println(user2.hashCode());
    }
    

    开启二级缓存,直接从工厂的缓存中读取:

    image-20220415145545556

    关闭二级缓存,重新查询:

    image-20220415145728360

6. 一对一关联查询

image-20220415150008835

在MyBatis的注解开发中对于多表查询==只支持分解查询==,不支持连接查询。

查询全部学生,每个学生中包含一个班级对象,属于一对一关系

查询全部学生,根据学生的班级id就可以查询每个班级

  1. 创建实体类

    public class Student {
        private int sid;
        private String name;
        private int age;
        private String sex;
        private Classes classes;
        // 省略getter/setter/toString
    }
    public class Classes {
        private int cid;
        private String className;
        private List<Student> students;
        // 省略getter/setter/toString
    }
    
  2. 创建分解后的查询方法

    public interface StudentMapper {
        @Select("select * from student")
        List<Student> findAll();
    }
    
    public interface ClassesMapper {
        // 根据id查询班级
        @Select("select * from classes where cid = #{cid}")
        Classes findByCid(Integer cid);
    }
    
  3. 主表的查询配置自定义映射关系

    在@Result内定义one属性表示该字段为对象类型

    @Select("select * from student")
    //自定义映射关系
    @Results(id = "studentMapper",value = {
        @Result(id = true,property = "sid",column = "sid"),
        @Result(property = "name",column = "name"),
        @Result(property = "age",column = "age"),
        @Result(property = "sex",column = "sex"),
        /**
                 * property:属性名
                 * column:调用从表方法时传入的参数列
                 * one:一对一关联,表示该属性是一个对象
                 * select:调用从表方法
                 * fetchType:加载方式
                 */
        @Result(property = "classes",column = "classId",
                one = @One(select = "com.sxt.mapper.ClassesMapper.findByCid",
                           fetchType = FetchType.EAGER))
    })
    List<Student> findAll();
    
  4. 测试

    @Test
    public void findAllStudent(){
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        List<Student> list = mapper.findAll();
        list.forEach(System.out::println);
    }
    

7. 一对多关联查询

查询所有班级,每个班级中都包含一个学生集合,属性一对多关系

查询所有班级,根据每个班级的ID查询学生

  1. 创建分解后的查询方法

    public interface ClassesMapper {
        // 查询所有班级
        @Select("select * from classes")
        List<Classes> findAll();
    }
    
    public interface StudentMapper {
        // 根据班级id查询学生
        @Select("select * from student where classId = #{classId}")
        List<Student> findByClassId(int classId);
    }
    
  2. 主表的查询配置自定义映射关系

    在@Result中many表示该属性是一个集合

    //查询所有班级
    @Select("select * from classes")
    @Results(id = "classMapper",value = {
        @Result(id = true,property = "cid",column = "cid"),
        @Result(property = "className",column = "className"),
        /**
                 * many:表示该属性是一个集合
                 */
        @Result(property = "students",column = "cid",
                many = @Many(select = "com.sxt.mapper.StudentMapper.findByClassId",
                             fetchType = FetchType.LAZY))
    })
    List<Classes> findAll();
    
  3. 测试

    @Test
    public void findAllClasses(){
        ClassesMapper mapper = session.getMapper(ClassesMapper.class);
        List<Classes> list = mapper.findAll();
        list.forEach(System.out::println);
    }
    

8. 注解开发与映射文件开发的对比

image-20220415153634966

MyBatis中更推荐使用映射文件开发,Spring、SpringBoot更推荐注解方式。具体使用要视项目情况而定。它们的优点对比如下:

映射文件:

  • 代码与Sql语句是解耦的,修改时只需修改配置文件,无需修改源码。
  • Sql语句集中,利于快速了解和维护项目。
  • 级联查询支持连接查询和分解查询两种方式,注解开发只支持分解查询。

注解:

  • 配置简单,开发效率高。
  • 类型安全,在编译期即可进行校验,不用等到运行时才发现错误。

十、PageHelper分页插件

image-20220415154016493

开发过程中如果要进行分页查询,需要传入页数和每页条数。返回页面数据,总条数,总页数,当前页面,每页条数等数据。此时使用PageHelper插件可以快速帮助我们获取这些数据。

image-20220415154328331

PageHelper是一款非常好用的开源免费的Mybatis第三方分页插件。使用该插件时,只要传入分页参数,即可自动生成页面对象。我们使用该插件分页查询所有用户:

  1. 引入依赖

    <!-- PageHelper -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>5.3.0</version>
    </dependency>
    
  2. Mybatis配置文件中配置PageHelper插件

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 设置数据库类型-->
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>
    
  3. 使用PageHelper插件

    PageHelper.startPage()中的两个参数分别代表:页数(从1开始),每页条数

    //测试分页插件
    @Test
    public void testFindPage(){
        //1.设置分页参数 参数1:页数,从1开始。参数2:每页条数
        PageHelper.startPage(1,3);
        //2.正常查询
        List<User> list = mapper.findAll();
        //3.封装查询结果,生成页面对象
        PageInfo pageInfo = new PageInfo(list);
        //4.打印页面对象的属性
        System.out.println("结果集:"+pageInfo.getList());
        System.out.println("总条数:"+pageInfo.getTotal());
        System.out.println("总页数:"+pageInfo.getPages());
        System.out.println("当前页:"+pageInfo.getPageNum());
        System.out.println("每页条数:"+pageInfo.getSize());
    }
    

十一、MyBatis Generator

image-20220415160528891

1. 工具引入

image-20220415160711270

MyBatis Generator(MBG)是MyBatis官方提供的代码生成器。它可以根据数据库的表结构自动生成POJO类、持久层接口与映射文件,极大减少了代码的编写量,提高开发效率。

MBG可以作为项目引入使用,也可以作为Maven插件使用,其中作为Maven插件使用更加方便快捷。

  1. 准备数据库表

    image-20220415160749601
  2. 在pom文件中配置MBG插件

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.7</version>
                <!--MBG配置-->
                <configuration>
                    <!--MBG配置文件位置-->
                    <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
                    <!--运行显示详情-->
                    <verbose>true</verbose>
                    <!--允许覆盖文件-->
                    <overwrite>true</overwrite>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
  3. 编写MBG配置文件

    <?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>
        <!--  JDBC的jar包位置,用于连接数据库  -->
        <classPathEntry location="C:\Developers\repository\mysql\mysql-connector-java\8.0.26\mysql-connector-java-8.0.26.jar"/>
    
        <context id="default" targetRuntime="MyBatis3">
            <!-- 是否去除自动生成的注释-->
            <commentGenerator>
                <property name="suppressAllComments" value="true"/>
            </commentGenerator>
    
            <!--数据库连接参数-->
            <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost:3306/mybatis"
                            userId="root"
                            password="123456">
            </jdbcConnection>
    
            <!-- 类型处理器,在数据库类型和java类型之间的转换控制-->
            <javaTypeResolver>
                <property name="forceBigDecimals" value="false"/>
            </javaTypeResolver>
    
            <!-- targetProject:JAVA类路径  targetProject:生成的POJO类的包-->
            <javaModelGenerator targetProject="src/main/java" targetPackage="com.itbaizhan.pojo">
                <!-- 是否生成子包 -->
                <property name="enableSubPackages" value="false"/>
                <!-- 设置是否在getter方法中,对String类型字段调用trim()方法 -->
                <property name="trimStrings" value="true"/>
            </javaModelGenerator>
    
            <!-- targetProject:配置文件路径 targetPackage:生成映射文件的位置 -->
            <sqlMapGenerator targetProject="src/main/resources" targetPackage="com.itbaizhan.mapper">
                <!-- 是否生成子包 -->
                <property name="enableSubPackages" value="false"/>
            </sqlMapGenerator>
    
            <!-- targetPackage:JAVA类路径 targetProject:生成的持久层接口包 -->
            <javaClientGenerator targetProject="src/main/java" targetPackage="com.itbaizhan.mapper" type="XMLMAPPER">
                <!-- 是否生成子包 -->
                <property name="enableSubPackages" value="false"/>
            </javaClientGenerator>
    
            <!-- 数据库表,表名不要和其他库中的表名一样 -->
            <table tableName="product"></table>
        </context>
    </generatorConfiguration>
    
  4. 运行插件,自动生成POJO,持久层接口,映射文件:

image-20220415164825527

image-20220415164856986

  • Product.java:POJO类
  • ProductMapper.java:持久层接口
  • ProductMapper.xml:映射文件
  • ProductExample.java:查询扩展类,该类可以构造复杂的查询条件。
    • Criterion:代表一个字段。
    • GeneratedCriteria:抽象类,生成查询条件的工具。
    • Criteria:GeneratedCriteria的子类,生成查询条件的工具
  1. 在配置文件中注册生成的映射文件

    <mappers>
        <mapper class="com.sxt.mapper.ProductMapper"></mapper>
    </mappers>
    

2. 增删改方法

image-20220415170408726

import com.sxt.mapper.ProductMapper;
import com.sxt.pojo.Product;
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.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

public class TestMBG {
    InputStream is = null;
    SqlSession session = null;
    ProductMapper mapper = null;

    @Before
    public void before() throws IOException {
        //【1】读取核心配置文件
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //【2】创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //【3】SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
        SqlSessionFactory factory = builder.build(is);
        //【4】SqlSessionFactory对象获取SqlSession对象
        session = factory.openSession();
        mapper = session.getMapper(ProductMapper.class);
    }

    @After
    public void after() throws IOException {
        //释放资源
        session.close();
        is.close();
    }

    //新增
    @Test
    public void testAdd(){
        Product product = new Product("百战python",15000.0);
        mapper.insert(product);
        session.commit();
    }

    //修改
    @Test
    public void testUpdate(){
        Product product = new Product(4,"百战python",25000.0);
        mapper.updateByPrimaryKey(product);
        session.commit();
    }

    //删除
    @Test
    public void testDelete(){
        mapper.deleteByPrimaryKey(4);
        session.commit();
    }
}

3. 查询方法

//根据ID查询
@Test
public void testFindById(){
    Product product = mapper.selectByPrimaryKey(1);
    System.out.println(product);
}

//查询所有
@Test
public void testFindByAll(){
    //查询扩展对象,可以构建查询条件
    ProductExample productExample = new ProductExample();
    List<Product> products = mapper.selectByExample(productExample);
    products.forEach(System.out::println);
}

//根据商品名查询
@Test
public void testFindByName(){
    //查询扩展对象,可以构建查询条件
    ProductExample productExample = new ProductExample();
    //构建查询条件    内部类
    ProductExample.Criteria criteria = productExample.createCriteria();
    criteria.andProductnameLike("%尚学堂%");
    //查询
    List<Product> products = mapper.selectByExample(productExample);
    products.forEach(System.out::println);
}

4. 复杂查询

//多条件and查询
@Test
public void testFindAnd(){
    //查询扩展对象,可以构建查询条件
    ProductExample productExample = new ProductExample();
    //构建查询条件
    ProductExample.Criteria criteria = productExample.createCriteria();
    criteria.andProductnameLike("%百战%");
    criteria.andPriceBetween(0.0,20000.0);

    //查询
    List<Product> products = mapper.selectByExample(productExample);
    products.forEach(System.out::println);
}

//多条件or查询
@Test
public void testFindOr(){
    //查询扩展对象,可以构建查询条件
    ProductExample productExample = new ProductExample();
    //构建查询条件
    ProductExample.Criteria criteria = productExample.createCriteria();
    criteria.andProductnameLike("%百战%");

    ProductExample.Criteria criteria1 = productExample.createCriteria();
    criteria1.andPriceBetween(0.0,10000.0);

    productExample.or(criteria1);

    //查询
    List<Product> products = mapper.selectByExample(productExample);
    products.forEach(System.out::println);
}

十二、动态代理

image-20220415172542799

1. 代理模式简介

image-20220415172631639

代理模式是23种设计模式之一。设计模式是前人总结的,在软件开发过程遇到常用问题的解决方案,常见的设计模式有单例模式、工厂模式、适配器模式等等。

代理模式的作用是在==不修改原对象的基础上增强该对象的方法==。比如官方购买苹果手机不赠送充电头,此时京东平台作为苹果的代理商,可以在代理销售苹果手机时赠送充电头。

代理模式分为静态代理、动态代理。静态代理会生成一个代理类,动态代理不会生成代理类,直接生成代理对象

2. JDK动态代理

image-20220415172714202

JDK动态代理是针对接口进行代理,所以我们要写被代理的接口和该接口的实现类。

  1. 被代理接口Apple.java

    package com.sxt.dynamic;
    
    /**
     * 被代理接口
     */
    public interface Apple {
        String sell(double price);//卖产品
    
        void repair();//维修
    }
    
  2. 苹果接口实现类AppleImpl.java

    package com.sxt.dynamic;
    
    /**
     * 被代理接口的实现类
     */
    public class AppleImpl implements Apple {
        @Override
        public String sell(double price) {
            System.out.println("产品卖了"+price+"元");
            return "iphone13";
        }
    
        @Override
        public void repair() {
            System.out.println("苹果售后维修");
        }
    }
    
  3. 代理方式类,定义被代理类的增强方式

    package com.sxt.dynamic;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 代理方式类,定义被代理类的增强方式
     * 该类实现InvocationHandler接口,重写invoke方法,定义方法的增强方式
     */
    public class ShoppingProxy implements InvocationHandler {
        private Apple apple;//被代理对象
    
        public ShoppingProxy(Apple apple) {
            this.apple = apple;
        }
    
        /**
         * 定义原方法的增强方式
         * @param proxy 被代理对象
         * @param method 被代理对象调用的方法
         * @param args 被代理对象调用方法时传入的参数
         * @return  方法的返回值
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String name = method.getName();//被代理对象执行的方法名
            if("sell".equals(name)){
                double price = (double) args[0] * 0.9;//增强参数
                Object result = method.invoke(apple, price);//执行方法
                return result + "和充电头";//增强返回值
            }else if("repair".equals(name)){
                System.out.println("专属客服为您服务!");//增强方法流程
                return method.invoke(apple,args);
            }else{
                return method.invoke(apple,args);   //什么都不增强
            }
        }
    }
    
  4. 测试类

    针对接口,调用newProxyInstance传入的是被代理接口

    package com.sxt.dynamic;
    
    import java.lang.reflect.Proxy;
    
    public class Test {
        public static void main(String[] args) {
            //被代理对象
            Apple apple = new AppleImpl();
            //代理方式对象
            ShoppingProxy shoppingProxy = new ShoppingProxy(apple);
            //生成代理对象
            Apple appleJD = (Apple) Proxy.newProxyInstance(
                    apple.getClass().getClassLoader(),//代理类的加载器
                    apple.getClass().getInterfaces(),//被代理接口
                    shoppingProxy //代理方式对象
            );
            //执行增强后的方法
            String sell = appleJD.sell(6000);
            System.out.println(sell);
    
            appleJD.repair();
        }
    }
    

3. CGLib动态代理

image-20220415172753030

CGLib动态代理简化了JDK动态代理的写法,JDK是针对接口代理,而CGLib是针对类代理

<!-- 引入cglib依赖 -->
<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>

被代理类Apple.java

package com.sxt.cglibdynamic;

/**
 * 被代理类
 */
public class Apple {
    public String sell(double price) {
        System.out.println("产品卖了"+price+"元");
        return "iphone13";
    }

    public void repair() {
        System.out.println("苹果售后维修");
    }
}

代理方式类ShoppingProxy,实现MethodInterceptor接口,重写intercept方法

package com.sxt.cglibdynamic;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 代理方式类,实现MethodInterceptor接口,重写intercept方法
 */
public class ShoppingProxy implements MethodInterceptor {
    private Apple apple;//被代理对象

    public ShoppingProxy(Apple apple){
        this.apple = apple;
    }

    /**
     * 定义原方法的增强方式
     * @param o 被代理对象
     * @param method 被代理对象调用的方法
     * @param objects 被代理对象调用方法时传入的参数
     * @param methodProxy 底层生成的代理类的应用
     * @return 方法的返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        String name = method.getName();
        if("sell".equals(name)){
            double price = (double)objects[0]*0.8;
            Object result = method.invoke(apple, price);
            return result+"和数据线";
        }else if("repair".equals(name)){
            System.out.println("专属客服为您服务!");
            return method.invoke(apple,objects);
        }else{
            return method.invoke(apple,objects);
        }
    }
}

测试类Test

package com.sxt.cglibdynamic;

import net.sf.cglib.proxy.Enhancer;

public class Test {
    public static void main(String[] args) {
        //被代理对象
        Apple apple = new Apple();
        //代理方式
        ShoppingProxy shoppingProxy = new ShoppingProxy(apple);
        //生成代理对象
        Apple appleTB = (Apple) Enhancer.create(Apple.class, shoppingProxy);

        //执行增强后的方法
        String sell = appleTB.sell(9000);
        System.out.println(sell);
        appleTB.repair();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

懿所思

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

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

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

打赏作者

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

抵扣说明:

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

余额充值