项目管理与SSM框架——MyBatis

MyBatis学习



一、MyBatis介绍

1.什么是框架

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

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

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

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

2.什么是ORM框架

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

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

1// 新增方法,将对象转为sql语句字段
2public void AddUser(User user) throws Exception {
    
    
3    Class.forName("com.mysql.jdbc.Driver");
4    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","root", "root");
5    String sql = "INSERT INTO user values (null,?,?,?,?)";
6
7    PreparedStatement preparedStatement = connection.prepareStatement(sql);
8    preparedStatement.setString(1,user.getName());
9    preparedStatement.setInt(2,user.getAge());
10    preparedStatement.setString(3,user.getAddress());
11    preparedStatement.setString(4,user.getSex());
12    preparedStatement.executeUpdate();
13    // 省略资源关闭...
14}
15
16
17// 查询方法,将数据库结果集转为对象
18public List<User> findAllUser() throws Exception {
    
    
19    Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
20    PreparedStatement preparedStatement = connection.prepareStatement("select * from user");
21    ResultSet resultSet = preparedStatement.executeQuery();
22
23    //遍历查询结果集
24    List<User> users = new ArrayList<>();
25    while(resultSet.next()){
    
    
26        // 拿到每一列数据
27        int id = resultSet.getInt("id");
28        String name = resultSet.getString("name");
29        int age = resultSet.getInt("age");
30        String address = resultSet.getString("address");
31        String sex = resultSet.getString("sex");
32        // 将数据封装到对象中
33        User user = new User();
34        user.setId(id);
35        user.setName(name);
36        user.setAge(age);
37        user.setAddress(address);
38        user.setSex(sex);
39        users.add(user);
40    }
41    // 省略资源关闭...
42    return users;
43}

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

3.什么是MyBatis

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

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

MyBatis与Hibernate的比较:
1.MyBatis是一个半自动的ORM框架,需要手写SQL语句。
2.Hibernate是一个全自动的ORM框架,不需要手写SQL语句。
3.使用MyBatis的开发量要大于Hibernate。

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

二、MyBatis入门案例

1.环境搭建

1.将SQL文件导入数据库

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

1<dependencies>
2    <!--  mybatis  -->
3    <dependency>
4        <groupId>org.mybatis</groupId>
5        <artifactId>mybatis</artifactId>
6        <version>3.5.7</version>
7    </dependency>
8    <!--  mysql驱动包  -->
9    <dependency>
10        <groupId>mysql</groupId>
11        <artifactId>mysql-connector-java</artifactId>
12        <version>8.0.26</version>
13    </dependency>
14
15    <!--  junit  -->
16    <dependency>
17        <groupId>junit</groupId>
18        <artifactId>junit</artifactId>
19        <version>4.10</version>
20    </dependency>
21    <!--  log4j  -->
22    <dependency>
23        <groupId>log4j</groupId>
24        <artifactId>log4j</artifactId>
25        <version>1.2.12</version>
26    </dependency>
27</dependencies>

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

1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE configuration
3        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
4        "http://mybatis.org/dtd/mybatis-3-config.dtd">
5<configuration>
6    <!--  配置环境  -->
7    <environments default="mysql">
8        <environment id="mysql">
9            <!--  事务类型  -->
10            <transactionManager type="JDBC"></transactionManager>
11            <!--  数据源  -->
12            <dataSource type="POOLED">
13                <property name="driver" value="com.mysql.jdbc.Driver"/>
14                <property name="url" value="jdbc:mysql:///mybatis"/>
15                <property name="username" value="root"/>
16                <property name="password" value="root"/>
17            </dataSource>
18        </environment>
19    </environments>
20</configuration>

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

5.创建实体类

1public class User {
    
    
2    private int id;
3    private String username;
4    private String sex;
5    private String address;
6    // 省略getter/setter/构造方法/toString方法
7}

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

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

1public interface UserMapper {
    
    
2    List<User> findAll();
3}

2.在resource目录创建映射文件

1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper
3        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5<mapper namespace="com.package.mapper.UserMapper">
6    <select id="findAll" resultType="com.package.pojo.User">
7        select * from user
8    </select>
9</mapper>

3.将映射文件配置到mybatis核心配置文件中

1<!--  注册映射文件  -->
2<mappers>
3    <mapper resource="com/package/mapper/UserMapper.xml">     </mapper>
4</mappers>

映射文件注意事项:

<映射文件要和接口名称相同。
<映射文件要和接口的目录结构相同。
<映射文件中namespace属性要写接口的全名。
<映射文件中标签的id属性是接口方法的方法名。
<映射文件中标签的resultType属性是接口方法的返回值类型。
<映射文件中标签的parameterType属性是接口方法的参数类型。
<映射文件中resultType、parameterType属性要写全类名,如果是集合类型,则写其泛型的全类名。

3.测试持久层接口方法

1@Test
2public void testFindAll() throws Exception {
    
    
3    // (1)读取核心配置文件
4    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
5    // (2)创建SqlSessionFactoryBuilder对象
6    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
7    // (3)SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
8    SqlSessionFactory factory = builder.build(is);
9    // (4)SqlSessionFactory对象获取SqlSession对象
10    SqlSession session = factory.openSession();
11    // (5)SqlSession对象获取代理对象
12    UserMapper userMapper = session.getMapper(UserMapper.class);
13    // (6)代理对象执行方法
14    List<User> all = userMapper.findAll();
15    all.forEach(System.out::println);
16
17    // (7)释放资源
18    session.close();
19    is.close();
20}

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

MyBatis核心对象
在这里插入图片描述
SqlSessionFactoryBuilder
SqlSession工厂构建者对象,使用构造者模式创建SqlSession工厂对象。

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

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

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

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

5.使用SQLSession操作数据库

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

1@Test
2public void testFindAll2() throws Exception {
    
    
3    // (1)读取核心配置文件
4    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
5    // (2)创建SqlSessionFactoryBuilder对象
6    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
7    // (3)SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
8    SqlSessionFactory factory = builder.build(is);
9    // (4)SqlSessionFactory对象获取SqlSession对象
10    SqlSession session = factory.openSession();
11    // (5)SqlSession直接操作数据库
12    List<User> users = session.selectList("com.package.mapper.UserMapper.findAll");
13    users.forEach(System.out::println);
14    // (6)关闭资源
15    session.close();
16    is.close();
17}

6.Mapper动态代理原理

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

获取代理对象
点开测试类的getMapper方法,查看该方法最终调用了什么方法。
在这里插入图片描述

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

查看代理方式
点开MapperProxy类,查看invoke方法,查看代理对象是如何工作的。
在这里插入图片描述
可以看到,MapperProxy调用了MapperMethod的execute方法定义了代理方式,且底层调用的是SqlSession的方法,根据映射文件标签不同调用不同的SqlSession方法。

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

三、MyBatis增删改查

1.新增

新增用户
1.持久层接口添加方法

1void add(User user);

2.映射文件添加标签

1<insert id="add" parameterType="com.package.pojo.User">
2    insert into user(username,sex,address) values(#{
    
    username},#{
    
    sex},#{
    
    address})
3</insert>

3.编写测试方法

1@Test
2public void testAdd() throws Exception {
    
    
3    InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
4    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
5    SqlSessionFactory factory = builder.build(is);
6    SqlSession session = factory.openSession();
7    UserMapper userMapper = session.getMapper(UserMapper.class);
8    User user = new User("程序员", "女", "上海");
9    userMapper.add(user);
10    // 提交事务
11    session.commit();
12    session.close();
13    is.close();
14}

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

2.修改

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

1InputStream is = null;
2SqlSession session = null;
3UserMapper userMapper = null;
4
5@Before
6public void before() throws IOException {
    
    
7    // (1)读取核心配置文件
8    is = Resources.getResourceAsStream("SqlMapConfig.xml");
9    // (2)创建SqlSessionFactoryBuilder对象
10    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
11    // (3)SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
12    SqlSessionFactory factory = builder.build(is);
13    // (4)SqlSessionFactory对象获取SqlSession对象
14    session = factory.openSession();
15    // (5)获取代理对象
16    userMapper = session.getMapper(UserMapper.class);
17}
18
19@After
20public void after() throws IOException {
    
    
21    // 释放资源
22    session.close();
23    is.close();
24}

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

3.删除

删除用户

1.持久层接口添加方法

1void delete(int userId);

2.映射文件添加标签

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

注:当方法的参数类型是简单数据类型时,#{}中可以写任意名称
简单数据类型:基本数据类型、字符串等

3.编写测试方法

1@Test
2public void testDelete(){
    
    
3    userMapper.delete(8);
4    session.commit();
5}

4.根据id查询

根据ID查询用户
1.持久层接口添加方法

1User findById(int userId);

2.映射文件添加标签

1<select id="findById" parameterType="int" resultType="com.package.pojo.User">
2    select * from user where id = #{
    
    userId}
3</select>

3.编写测试方法

1@Test
2public void testFindById(){
    
    
3    User user = userMapper.findById(1);
4    System.out.println(user);
5}

5.模糊查询

使用#定义参数
1.持久层接口添加方法

1List<User> findByNameLike(String username);

2.映射文件添加标签

1<select id="findByNameLike" parameterType="string" resultType="com.package.user.User">
2    select * from user where username like #{
    
    name}
3</select>

3.编写测试方法

1@Test
2public void testFindByNameLike(){
    
    
3    List<User> users = userMapper.findByNameLike("%王%");
4    for (User user:users){
    
    
5        System.out.println(user);
6    }
7}

我们看到在映射文件中,parameterType的值为string而没有写java.lang.String,这是为什么呢?
参数/返回值类型为基本数据类型/包装类/String等类型时,我们可以写全类名,也可以写别名。

数据类型 别名
byte _byte
long _long
short _short
int _int
int _integer
double _double
float _float
boolean _boolean
String string
Byte byte
Long long
Short short
Integer int/integer
Double double
Float float
Boolean boolean
Date date
BigDecimal decimal/bigdecimal
Object object
Map map
HashMap hashmap
List list
ArrayList arraylist
Collection collection
Iterator iterator

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

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

测试方法写法如下:

1@Test
2public void testFindByNameLike(){
    
    
3    List<User> users = userMapper.findByUsernameLike("可爱");
4    users.forEach(System.out::println);
5}

#和$的区别:

1.#表示sql模板的占位符,$表示将字符串拼接到sql模板中。

2.#可以防止sql注入,一般能用#就不用$。

3.${}内部的参数名必须写value。

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

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

测试方法写法如下:

1@Test
2public void testFindByNameLike(){
    
    
3    List<User> users = userMapper.findByUsernameLike("有趣");
4    users.forEach(System.out::println);
5}

6.分页查询

在这里插入图片描述

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

顺序传参
Sql中的参数使用arg0,arg1…或param1,param2…表示参数的顺序。

1.持久层接口方法

1/**
2     * 分页查询
3     * @param startIndex 开始索引
4     * @param pageSize 每页条数
5     * @return
6     */
7List<User> findPage(int startIndex,int pageSize);

2.映射文件

1<select id="findPage" resultType="com.package.mapper.User">
2    select * from user limit #{
    
    arg0},#{
    
    arg1}
3</select>
4
5<select id="findPage" resultType="com.package.mapper.User">
6    select * from user limit #{
    
    param1},#{
    
    param2}
7</select>

3.测试类

1@Test
2public void testFindPage(){
    
    
3    List<User> users = userMapper.findPage(0,3);
4    users.forEach(System.out::println);
5}

@Param传参
在接口方法的参数列表中通过@Param定义参数名称,在Sql语句中通过注解中所定义的参数名称指定参数位置。

1.持久层接口方法

1List<User> findPage1(@Param("startIndex") int startIndex, @Param("pageSize")int pageSize);

2.映射文件

1<select id="findPage1" resultType="com.package.mapper.User">
2  select * from user limit #{
    
    startIndex},#{
    
    pageSize}
3</select>

3.测试类

1@Test
2public void testFindPage1(){
    
    
3    List<User> users = userMapper.findPage1(3,3);
4    users.forEach(System.out::println);
5}

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

1.自定义POJO

1public class PageQuery {
    
    
2    private int startIndex;
3    private int pageSize;
4    // 省略getter/setter/构造方法
5}

2.持久层接口方法

1List<User> findPage2(PageQuery pageQuery);

3.映射文件

1<select id="findPage2" resultType="com.package.pojo.User" parameterType="com.package.pojo.PageQuery">
2    select * from user limit #{
    
    startIndex},#{
    
    pageSize}
3</select>

4.测试类

1@Test
2public void testFindPage2(){
    
    
3    PageQuery pageQuery = new PageQuery(3, 3);
4    List<User> users = userMapper.findPage2(pageQuery);
5    users.forEach(System.out::println);
6}

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

1.持久层接口方法

1List<User> findPage3(Map<String,Object> params);

2.映射文件

1<select id="findPage3" resultType="com.package.pojo.User" parameterType="map">
2    select * from user limit #{
    
    startIndex},#{
    
    pageSize}
3</select>

3.测试类

1@Test
2public void testFindPage3(){
    
    
3    Map<String,Object> params = new HashMap();
4    params.put("startIndex",0);
5    params.put("pageSize",4);
6    List<User> users = userMapper.findPage3(params);
7    users.forEach(System.out::println);
8}

7.聚合查询

查询用户总数

1.持久层接口方法

1int findCount();

2.映射文件

1<select id="findCount" resultType="int">
2    select count(id) from user
3</select>

3.测试类

1@Test
2public void testFindCount(){
    
    
3    System.out.println(userMapper.findCount());
4}

8.主键回填

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

1.持久层接口方法

1void add(User user);

2.映射文件

1<insert id="add" parameterType="com.package.user.User">
2    <!-- keyProperty:主键属性名,keyColumn:主键列名,resultType:主键类型,order:执行时机 -->
3    <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
4        SELECT LAST_INSERT_ID();
5    </selectKey>
6    insert into user(username,birthday,sex,address) values(#{
    
    username},#{
    
    birthday},#{
    
    sex},#{
    
    address})
7</insert>

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

3.测试类

1@Test
2public void testAdd(){
    
    
3    User user = new User("小可爱", new Date(), "男", "北京");
4    userMapper.add(user);
5    session.commit();
6    System.out.println(user.getId());
7}

四、MyBatis配置文件

MyBatis配置文件结构:

1-configuration
2    -properties(属性)
3        -property
4    -settings(全局配置参数)
5        -setting
6    -plugins(插件)
7        -plugin
8    -typeAliases(别名)
9        -typeAliase
10        -package
11    -environments(环境)
12        -environment
13            -transactionManager(事务管理)
14            -dataSource(数据源)
15    -mappers(映射器)
16        -mapper
17        -package

1.properties

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

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

1.编写db.properties

1jdbc.driver=com.mysql.jdbc.Driver
2jdbc.url=jdbc:mysql://localhost:3306/mybatis
3jdbc.username=root
4jdbc.password=root

2.在配置文件中引入db.properties

1<properties resource="db.properties"></properties>
2<environments default="mysql">
3    <environment id="mysql">
4        <transactionManager type="JDBC"></transactionManager>
5        <dataSource type="POOLED">
6            <property name="driver" value="${jdbc.driver}"/>
7            <property name="url" value="${jdbc.url}"/>
8            <property name="username" value="${jdbc.username}"/>
9            <property name="password" value="${jdbc.password}"/>
10        </dataSource>
11    </environment>
12</environments>

当然我们也可以将数据源数据通过<properties>配置到MyBatis配置文件内。

1<properties>
2    <property name="jdbc.driver" value="com.mysql.jdbc.Driver"></property>
3    <property name="jdbc.url" value="jdbc:mysql://localhost:3306/mybatis"></property>
4    <property name="jdbc.username" value="root"></property>
5    <property name="jdbc.password" value="root"></property>
6</properties>
7
8<environments default="mysql">
9    <environment id="mysql">
10        <transactionManager type="JDBC"></transactionManager>
11        <dataSource type="POOLED">
12            <property name="driver" value="${jdbc.driver}"/>
13            <property name="url" value="${jdbc.url}"/>
14            <property name="username" value="${jdbc.username}"/>
15            <property name="password" value="${jdbc.password}"/>
16        </dataSource>
17    </environment>
18</environments>

2.settings

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

3.plugins

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

4.typeAliases

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

为一个类配置别名

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

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

1.配置文件:

1<typeAliases>
2    <typeAlias type="com.package.pojo.User" alias="User">     </typeAlias>
3</typeAliases>

2.映射文件:

1<select id="findAll" resultType="User">
2    select * from user
3</select>

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

1<typeAliases>
2    <package name="包名"></package>
3</typeAliases>

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

1.配置文件:

1<typeAliases>
2    <package name="com.package.pojo"></package>
3</typeAliases>

2.映射文件:

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

5.environments

可以为MyBatis配置数据环境。

事务管理

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

连接池

1<environments default="mysql">
2    <environment id="mysql">
3        <transactionManager type="JDBC"></transactionManager>
4        <!-- 连接池设置 -->
5        <dataSource type="POOLED">
6            <!-- 数据源设置... -->
7        </dataSource>
8    </environment>
9</environments>

dataSource的type属性:

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

6.mappers

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

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

1<mappers>
2  <mapper resource="com/package/mapper/UserMapper.xml"/>
3</mappers>

2.使用绝对路径注册映射文件

1<mappers>   
2    <mapper url="file:///D:\code\webproject\mybatis\mybatiscase\mybatiseDemo1\src\main\resources\com\package\mapper\UserMapper.xml"/> 
3</mappers>

3.注册持久层接口

1<mappers>   
2    <mapper class="com.package.mapper.UserMapper"/> 
3</mappers>

4.注册一个包下的所有持久层接口

1<mappers>
2    <package name="com.package.mapper"/>
3</mappers>

五、MyBatis映射文件

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

1.resultMap

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

MyBatis可以将数据库结果集封装到对象中,是因为结果集的列名和对象属性名相同:
在这里插入图片描述

当POJO属性名和数据库列名不一致时,MyBatis无法自动完成映射关系。如:
在这里插入图片描述

此时有两种解决方案:

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

1<select id="findAll" resultType="com.package.pojo.Teacher">
2    select tid as id,tname as teacherName from teacher;
3</select>

2.自定义映射关系

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

1<!-- id:自定义映射名 type:自定义映射的对象类型   -->
2<resultMap id="teacherMapper" type="com.package.pojo.Teacher">
3    <!-- id定义主键列  property:POJO属性名 column:数据库列名  -->
4    <id property="id" column="tid"></id>
5    <!-- result定义普通列  property:POJO属性名 column:数据库列名  -->
6    <result property="teacherName" column="tname"></result>
7</resultMap>

<select>标签中,使用resultMap属性代替resultType属性,使用自定义映射关系。

1<select id="findAll" resultMap="teacherMapper">
2    select * from teacher
3</select>

2.sql、include

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

<sql id="selectAllField">
    select tid as id,tname as teacherName
</sql>
<select id="findAll" resultType="com.package.pojo.Teacher">
    <include refid="selectAllField"></include>
    from teacher;
</select>
<select id="findById" resultType="com.package.pojo.Teacher">
    <include refid="selectAllField"></include>
    from teacher where tid = #{
    
    id}
</select>

3.特殊字符处理

在Mybatis映射文件中尽量不要使用一些特殊字符,如:<>等。

我们可以使用符号的实体来表示:

符号 实体
< &lt;
> &gt;
& &amp;
&apos;
&quot;
如:
1<select id="findById2" resultType="com.package.pojo.Teacher">
2    <include refid="selectAllField"></include>
3    from teacher where tid &gt; #{
    
    id}
4</select>

六、MyBatis动态Sql

在这里插入图片描述

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

1.if

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

1.持久层接口添加方法

1// 用户通用查询
2List<User> findByCondition(User user);

2.映射文件添加标签

1<select id="findByCondition" parameterType="com.package.pojo.User" resultType="com.package.pojo.User">
2    select * from user where 1 = 1
3    <if test="username != null and username.length() != 0">
4        and username like #{
    
    username}
5    </if>
6    <if test="sex != null and sex.length() != 0">
7        and sex = #{
    
    sex}
8    </if>
9    <if test="address != null and address.length() != 0">
10        and address = #{
    
    address}
11    </if>
12</select>

3.编写测试方法

1@Test
2public void testFindByCondition(){
    
    
3    User user = new User();
4    List<User> users1 = userMapper2.findByCondition(user);
5    //users1.forEach(System.out::println);
6
7    user.setUsername("%JSON%");
8    List<User> users2 = userMapper2.findByCondition(user);
9    users2.forEach(System.out::println);
10
11    user.setAddress("earth");
12    List<User> users3 = userMapper2.findByCondition(user);
13    users3.forEach(System.out::println);
14}

1.if中的条件不能使用&&/||,而应该使用and/or

2.if中的条件可以直接通过属性名获取参数POJO的属性值,并且该值可以调用方法。

3.where后为什么要加1=1?
任意条件都可能拼接到Sql中。如果有多个条件,从第二个条件开始前都需要加And关键字。加上1=1这个永久成立的条件,就不需要考虑后面的条件哪个是第一个条件,后面的条件前都加And关键字即可。

2.where

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

1<select id="findByCondition" resultType="com.package.user.User" parameterType="com.package.user.User">
2    select * from user
3    <where>
4        <if test="username != null and username.length() != 0">
5            username like #{
    
    username}
6        </if>
7        <if test="sex != null and sex.length() != 0">
8            and sex = #{
    
    sex}
9        </if>
10    </where>
11</select>

3.set

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

1<update id="update" parameterType="com.package.user.User">
2    update user
3    <set>
4        <if test="username != null and username.length() > 0">
5            username = #{
    
    username},
6        </if>
7        <if test="sex != null and sex.length() > 0">
8            sex = #{
    
    sex},
9        </if>
10    </set>
11    <where>
12        id = #{
    
    id}
13    </where>
14</update>

4.when、choose、otherwise

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

1<select id="findByCondition" resultType="com.package.user.User" parameterType="com.package.user.User">
2    select * from user
3    <where>
4        <choose>
5            <when test="username.length() &lt; 5">
6                username like #{
    
    username}
7            </when>
8            <when test="username.length() &lt; 10">
9                username = #{
    
    username}
10            </when>
11            <otherwise>
12                id = 1
13            </otherwise>
14        </choose>
15    </where>
16</select>

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

5.foreach

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

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

遍历数组
我们使用遍历数组进行批量删除。

1.持久层接口添加方法

1void deleteBatch(int[] ids);

2.映射文件添加标签

1<delete id="deleteBatch" parameterType="int">
2    delete from user
3    <where>
4        <foreach open="id in(" close=")" separator="," collection="array" item="id" >
5            #{
    
    id}
6        </foreach>
7    </where>
8</delete>

3.编写测试方法

1@Test
2public void testDeleteBatch(){
    
    
3    int[] ids = {
    
    9,11};
4    userMapper.deleteBatch(ids);
5    session.commit();
6}

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

1.持久层接口添加方法

1void insertBatch(List<User> users);

2.映射文件添加标签

1<insert id="insertBatch" parameterType="com.package.user.User">
2    insert into user values
3    <foreach collection="list" item="user" separator=",">
4        (null ,#{
    
    user.username},#{
    
    user.birthday},#{
    
    user.sex},#{
    
    user.address})
5    </foreach>
6</insert>

3.编写测试方法

1@Test
2public void testDeleteBatch(){
    
    
3    int[] ids = {
    
    1,2,3};
4    userMapper.deleteBatch(ids);
5    session.commit();
6}

遍历Map
我们使用遍历Map进行多条件查询。

1.持久层接口添加方法

1/**
2     * 多条件查询
3     * @param map 查询的条件键值对 键:属性名 值:属性值
4     * @return
5     */
6List<User> findUser(@Param("queryMap") Map<String,Object> map);

2.映射文件添加标签

1<select id="findUser" parameterType="map" resultType="com.package.pojo.User">
2    select * from user
3    <where>
4        <foreach collection="queryMap" separator="and" index="key" item="value">
5            ${
    
    key} = #{
    
    value}
6        </foreach>
7    </where>
8</select>

3.编写测试方法

1@Test
2public void testFindUser(){
    
    
3    Map<String,Object> queryMap = new HashMap();
4    queryMap.put("sex","女");
5    queryMap.put("address","Mars");
6    List<User> users = userMapper2.findUser(queryMap);
7    users.forEach(System.out::println);
8}

七、MyBatis缓存

1.缓存介绍

在这里插入图片描述

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

什么是缓存?
存在于内存中的一块数据。

缓存有什么作用?
减少程序和数据库的交互,提高查询效率,降低服务器和数据库的压力。

什么样的数据使用缓存?
经常查询但不常改变的,改变后对结果影响不大的数据。

MyBatis缓存分为哪几类?
一级缓存和二级缓存

如何判断两次Sql是相同的?
1.查询的Sql语句相同
2.传递的参数值相同
3.对结果集的要求相同
4.预编译的模板Id相同

2.MyBatis一级缓存

在这里插入图片描述
MyBatis一级缓存也叫本地缓存。SqlSession对象中包含一个Executor对象,Executor对象中包含一个PerpetualCache对象,在该对象存放一级缓存数据。

由于一级缓存是在SqlSession对象中,所以只有使用同一个SqlSession对象操作数据库时才能共享一级缓存。

MyBatis的一级缓存是默认开启的,不需要任何的配置。

测试一级缓存

1@Test
2public void testCache1() throws IOException {
    
    
3    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
4    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
5    SqlSessionFactory factory = builder.build(is);
6    SqlSession session = factory.openSession();
7
8    // 使用同一个SqlSession查询
9    UserMapper mapper1 = session.getMapper(UserMapper.class);
10    UserMapper mapper2 = session.getMapper(UserMapper.class);
11
12    User user1 = mapper1.findById(1);
13    System.out.println(user1.hashCode());
14    System.out.println("-------------------------------------------");
15    User user2 = mapper2.findById(1);
16    System.out.println(user2.hashCode());
17}
18
19@Test
20public void testCache2() throws IOException {
    
    
21    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
22    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
23    SqlSessionFactory factory = builder.build(is);
24    SqlSession session1 = factory.openSession();
25    SqlSession session2 = factory.openSession();
26
27    // 使用不同的SqlSession查询
28    UserMapper mapper1 = session1.getMapper(UserMapper.class);
29    UserMapper mapper2 = session2.getMapper(UserMapper.class);
30
31    User user1 = mapper1.findById(1);
32    System.out.println(user1.hashCode());
33    System.out.println("-------------------------------------------");
34    User user2 = mapper2.findById(1);
35    System.out.println(user2.hashCode());
36}

3.清空一级缓存

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

1.SqlSession调用close():操作后SqlSession对象不可用,该对象的缓存数据也不可用。
2.SqlSession调用clearCache()/commit():操作会清空一级缓存数据。
3.SqlSession调用增删改方法:操作会清空一级缓存数据,因为增删改后数据库发生改变,缓存数据将不准确。

1@Test
2public void testCache3() throws IOException {
    
    
3    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
4    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
5    SqlSessionFactory factory = builder.build(is);
6    SqlSession session = factory.openSession();
7
8    UserMapper mapper1 = session.getMapper(UserMapper.class);
9    UserMapper mapper2 = session.getMapper(UserMapper.class);
10
11    User user1 = mapper1.findById(1);
12    System.out.println(user1.hashCode());
13    //        session.close();
14    //        session.clearCache();
15    //        session.commit();
16    mapper1.delete(2);
17    System.out.println("-------------------------------------------");
18    User user2 = mapper2.findById(1);
19    System.out.println(user2.hashCode());
20}

4.MyBatis二级缓存

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

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

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

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

开启二级缓存
1.POJO类实现Serializable接口。

1public class User implements Serializable {
    
    
2    private int id;
3    private String username;
4    private String sex;
5    private String address;
6}

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

1<settings>
2    <setting name="cacheEnabled" value="true"/>
3</settings>

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

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

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

1<cache size="2048"/>

4.测试二级缓存

1@Test
2public void testCache4() throws IOException {
    
    
3    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
4    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
5    SqlSessionFactory factory = builder.build(is);
6    SqlSession session1 = factory.openSession();
7    SqlSession session2 = factory.openSession();
8
9    UserMapper mapper1 = session1.getMapper(UserMapper.class);
10    UserMapper mapper2 = session2.getMapper(UserMapper.class);
11
12    User user1 = mapper1.findById(1);
13    System.out.println(user1);
14    System.out.println(user1.hashCode());
15    // 让一级缓存失效
16    session1.commit();
17    System.out.println("-------------------------------------------");
18
19    User user2 = mapper2.findById(1);
20    System.out.println(user2);
21    System.out.println(user2.hashCode());
22}

八、MyBatis关联查询

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

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

例如有学生类和班级类:

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

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

实体类设计如下:

1public class Student {
    
    
2    private int sid;
3    private String name;
4    private int age;
5    private String sex;
6    private Classes classes;
7    // 省略getter/setter/toString
8}
9
10public class Classes {
    
    
11    private int cid;
12    private String className;
13    private List<Student> studentList;
14    // 省略getter/setter/toString
15}

数据库设计如下:
在这里插入图片描述

1.一对一关联查询

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

创建持久层接口

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

创建映射文件

1<resultMap id="studentMapper" type="com.package.pojo.Student">
2    <!-- 主键列 -->
3    <id property="sid" column="sid"></id>
4    <!-- 普通列 -->
5    <result property="name" column="name"></result>
6    <result property="age" column="age"></result>
7    <result property="sex" column="sex"></result>
8    <!-- 一对一对象列 property:属性名  column:关联列名 javaType:对象类型-->
9    <association property="classes" column="classId" javaType="com.package.pojo.Classes">
10        <!-- 关联对象主键列 -->
11        <id property="cid" column="cid"></id>
12        <!-- 关联对象普通列 -->
13        <result property="className" column="className"></result>
14    </association>
15</resultMap>
16
17<!-- 多表查询,级联查询学生和其班级 -->
18<select id="findAll" resultMap="studentMapper">
19    select * from student left join classes on student.classId = classes.cid;
20</select>

配置文件注册映射文件

1<mappers>
2    <package name="com.package.mapper"/>
3</mappers>

测试一对一关联查询

1InputStream is = null;
2SqlSession session = null;
3
4@Before
5public void before() throws IOException {
    
    
6    is = Resources.getResourceAsStream("SqlMapConfig.xml");
7    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
8    SqlSessionFactory factory = builder.build(is);
9    session = factory.openSession();
10}
11
12@After
13public void after() throws IOException {
    
    
14    session.close();
15    is.close();
16}
17
18@Test
19public void testFindAllStudent(){
    
    
20    StudentMapper studentMapper = session.getMapper(StudentMapper.class);
21    List<Student> all = studentMapper.findAll();
22    all.forEach(System.out::println);
23}

2.一对多关联查询

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

创建持久层接口

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

创建映射文件

1<resultMap id="classesMapper" type="com.package.pojo.Classes">
2    <id property="cid" column="cid"></id>
3    <result property="className" column="className"></result>
4    <!-- 集合列  property:属性名  column:关联列名 ofType:集合的泛型 -->
5    <collection property="studentList" column="classId" ofType="com.package.pojo.Student">
6        <id property="sid" column="sid"></id>
7        <result property="name" column="name"></result>
8        <result property="age" column="age"></result>
9        <result property="sex" column="sex"></result>
10    </collection>
11</resultMap>
12
13<!-- 多表查询,级联查询班级和它的学生 -->
14<select id="findAll" resultMap="classesMapper">
15    select * from classes left join student  on classes.cid = student.classId;
16</select>

测试一对多关联查询

1@Test
2public void testFindAllClasses() {
    
    
3    ClassesMapper classesMapper = session.getMapper(ClassesMapper.class);
4    List<Classes> all = classesMapper.findAll();
5    all.forEach(System.out::println);
6}

3.多对多关联查询

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

例如有老师类和班级类:

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

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

实体类设计如下:

1public class Teacher {
    
    
2    private Integer tid;
3    private String tname;
4    private List<Classes> classes;
5    // 省略getter/setter/toString
6}
7
8public class Classes {
    
    
9    private Integer cid;
10    private String className;
11    private List<Student> studentList;
12    private List<Teacher> teacherList;
13    // 省略getter/setter/toString
14}

在数据库设计中,需要建立中间表,双方与中间表均为一对多关系。
在这里插入图片描述

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

创建持久层接口

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

创建映射文件

1<resultMap id="teacherMapper" type="com.package.pojo.Teacher">
2    <id column="tid" property="tid"></id>
3    <result column="tname" property="tname"></result>
4    <collection property="classes" column="tid" ofType="com.package.pojo.Classes">
5        <id column="cid" property="cid"></id>
6        <result column="className" property="className"></result>
7    </collection>
8</resultMap>
9
10<select id="findAll" resultMap="teacherMapper">
11    select *
12    from teacher
13    left join classes_teacher
14    on teacher.tid = classes_teacher.tid
15    left join classes
16    on classes_teacher.cid = classes.cid
17</select>

测试多对多关联查询

1@Test
2public void testFindAllTeacher() {
    
    
3    TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);
4    List<Teacher> all = teacherMapper.findAll();
5    all.forEach(System.out::println);
6}

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

1<resultMap id="classesMapper" type="com.package.pojo.Classes">
2    <id property="cid" column="cid"></id>
3    <result property="className" column="className"></result>
4    <!-- 集合列  property:属性名  column:关联列名 ofType:集合的泛型 -->
5    <collection property="studentList" column="classId" ofType="com.package.pojo.Student">
6        <id property="sid" column="sid"></id>
7        <result property="name" column="name"></result>
8        <result property="age" column="age"></result>
9        <result property="sex" column="sex"></result>
10    </collection>
11    <collection property="teacherList" column="cid" ofType="com.package.pojo.Teacher">
12        <id property="tid" column="tid"></id>
13        <result property="tname" column="tname"></result>
14    </collection>
15</resultMap>
16
17<select id="findAll" resultMap="classesMapper">
18    select *
19    from classes
20    left join student
21    on classes.cid = student.classId
22    left join classes_teacher
23    on classes.cid = classes_teacher.cid
24    left join teacher
25    on classes_teacher.tid = teacher.tid;
26</select>

4.一对一分解式查询

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

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

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

1public interface StudentMapper {
    
    
2     // 查询所有学生
3    List<Student> findAll();
4}
5
6public interface ClassesMapper {
    
    
7    // 根据ID查询班级
8    Classes findByCid(int cid);
9}

在映射文件中进行配置

1<select id="findAll" resultType="com.package.pojo.Student">
2    select *
3    from student
4</select>
5
6<select id="findByCid" resultType="com.package.pojo.Classes" parameterType="int">
7    select * from classes where cid = ${
    
    cid}
8</select>

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

1<resultMap id="MyStudentMapper" type="com.package.pojo.Student">
2    <id property="sid" column="sid"></id>
3    <result property="name" column="name"></result>
4    <result property="age" column="age"></result>
5    <result property="sex" column="sex"></result>
6    <association property="classes"
7                 javaType="com.package.pojo.Classes"
8                 select="com.package.mapper2.ClassesMapper2.findByCid"
9                 column="classId">
10    </association>
11</resultMap>
12
13<select id="findAll" resultMap="MyStudentMapper">
14    select *
15    from student
16</select>

测试查询方法

1@Test
2public void testFindAllStudent2(){
    
    
3    StudentMapper2 studentMapper2 = session.getMapper(StudentMapper2.class);
4    List<Student> all = studentMapper2.findAll();
5    all.forEach(System.out::println);
6}

5.一对多分解式查询

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

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

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

1# 查询班级时关联查询出学生
2select * from classes;
3select * from student where classId = 1;
4select * from student where classId = 2;  

这种写法也叫N+1查询。

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

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

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

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

1public interface ClassesMapper {
    
    
2    // 查询所有班级
3    List<Classes> findAll();
4}
5
6public interface StudentMapper {
    
    
7    // 根据班级Id查询学生
8    List<Student> findByClassId(int classId);
9}

在映射文件中进行配置

1<select id="findAll" resultType="com.package.pojo.Classes">
2    select * from classes
3</select>
4
5<select id="findByClassId" resultType="com.package.pojo.Student" parameterType="int">
6    select * from student where classId = ${
    
    classId}
7</select>

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

1<!-- 自定义映射关系   -->
2<resultMap id="MyClassesMapper" type="com.package.pojo.Classes">
3    <id property="cid" column="cid"></id>
4    <result property="className" column="className"></result>
5    <!-- select:从表查询调用的方法  column:调用方法时传入的参数字段    -->
6    <collection property="studentList"
7                ofType="com.package.pojo.Student"             select="com.package.mapper2.StudentMapper2.findByClassId"
8                column="cid">
9    </collection>
10</resultMap>
11
12<select id="findAll" resultMap="MyClassesMapper">
13    select * from classes
14</select>

测试查询方法

1@Test
2public void testFindAllClasses2(){
    
    
3    ClassesMapper2 classesMapper2 = session.getMapper(ClassesMapper2.class);
4    List<Classes> all = classesMapper2.findAll();
5    all.forEach(System.out::println);
6}

6.延迟加载

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

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

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

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

1<settings>
2    <setting name="lazyLoadingEnabled" value="true"/>
3</settings>

设置某个方法为延迟加载:

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

测试延迟加载

1@Test
2public void testFindAllClasses2(){
    
    
3    ClassesMapper2 classesMapper2 = session.getMapper(ClassesMapper2.class);
4    List<Classes> all = classesMapper2.findAll();
5    all.forEach(System.out::println);
6    System.out.println("-------------------------");
7    System.out.println(all.get(0).getStudentList());
8}

由于打印对象时会调用对象的toString方法,toString方法默认会触发延迟加载的查询,所以我们无法测试出延迟加载的效果。
解决方法:
我们在配置文件设置lazyLoadTriggerMethods属性,该属性指定对象的什么方法触发延迟加载,设置为空字符串即可。

1<settings>
2    <setting name="lazyLoadTriggerMethods" value=""/>
3</settings>

什么时候使用延迟加载?
一般情况下,一对多查询使用延迟加载,一对一查询使用立即加载。

九、MyBatis注解开发

MyBatis可以使用注解替代映射文件。映射文件的作用就是定义Sql语句,可以在持久层接口上使用@Select/@Delete/@Insert/@Update定义Sql语句,这样就不需要使用映射文件了。

1.环境搭建

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

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

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

4.创建实体类

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

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

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

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

1<mappers>
2    <package name="com.package.mapper"/>
3</mappers>

7.测试方法

1InputStream is = null;
2SqlSession session = null;
3UserMapper userMapper = null;
4
5@Before
6public void before() throws IOException {
    
    
7    is = Resources.getResourceAsStream("SqlMapConfig.xml");
8    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
9    SqlSessionFactory factory = builder.build(is);
10    session = factory.openSession();
11    userMapper = session.getMapper(UserMapper.class);
12}
13
14@After
15public void after() throws IOException {
    
    
16    session.close();
17    is.close();
18}
19
20
21@Test
22public void testFindAll(){
    
    
23    List<User> all = userMapper.findAll();
24    all.forEach(System.out::println);
25}

2.增删改查

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

1@SelectKey(keyColumn = "id", keyProperty = "id", resultType = int.class,before = false, statement = "SELECT LAST_INSERT_ID()")
2@Insert("insert into user(username,sex,address) values(#{username},#{sex},#{address})")
3void add(User user);
4
5@Update("update user set username = #{username},sex=#{sex},address=#{address} where id = #{id}")
6void update(User user);
7
8@Delete("delete from user where id = #{id}")
9void delete(int id);
10
11@Select("select * from user where username like #{username}")
12List<User> findByUsernameLike(String username);

3.动态sql

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

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

1// 根据任意条件查询
2@Select("<script>" +
3        "   select * from user\n" +
4        "        <where>\n" +
5        "            <if test=\"username != null and username.length() != 0\">\n" +
6        "                username like #{username}\n" +
7        "            </if>\n" +
8        "            <if test=\"sex != null and sex.length() != 0\">\n" +
9        "                and sex = #{sex}\n" +
10        "            </if>\n" +
11        "            <if test=\"address != null and address.length() != 0\">\n" +
12        "                and address = #{address}\n" +
13        "            </if>\n" +
14        "        </where>" +
15        "</script>")
16List<User> findByCondition(User user);

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

1// 生成根据任意条件查询的Sql语句
2public String findByConditionSql(User user){
    
    
3    StringBuffer sb = new StringBuffer("select * from user where 1=1 ");
4    if (user.getUsername() != null && user.getUsername().length() != 0){
    
    
5        sb.append(" and username like #{username} ");
6    }
7    if (user.getSex() != null && user.getSex().length() != 0){
    
    
8        sb.append(" and sex = #{sex} ");
9    }
10    if (user.getAddress() != null && user.getAddress().length() != 0){
    
    
11        sb.append(" and address = #{address} ");
12    }
13    return sb.toString();
14}

4.自定义映射关系

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

1// 查询所有用户
2@Results(id = "userDiyMapper" ,value = {
    
    
3    @Result(id = true,property = "id",column = "id"),
4    @Result(property = "username",column = "username1"),
5    @Result(property = "sex",column = "sex1"),
6    @Result(property = "address",column = "address1"),
7})
8@Select("select * from user")
9List<User> findAll();
10
11// 根据id查询
12@ResultMap("userDiyMapper")
13@Select("select * from user where id = #{id}")
14User findById(int id);

5.二级缓存

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

1.POJO类实现Serializable接口。

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

1<settings>
2    <setting name="cacheEnabled" value="true"/>
3</settings>

3.在持久层接口上方加注解@CacheNamespace(blocking=true),该接口的所有方法都支持二级缓存。

4.测试二级缓存

1@Test
2public void testCache() throws IOException {
    
    
3    InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
4    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
5    SqlSessionFactory factory = builder.build(is);
6    SqlSession session1 = factory.openSession();
7    SqlSession session2 = factory.openSession();
8
9    User user1 = session1.getMapper(UserMapper.class).findById(1);
10    System.out.println(user1);
11    System.out.println(user1.hashCode());
12    session1.commit(); // 清空一次缓存,将数据存到二级缓存
13    User user2 = session2.getMapper(UserMapper.class).findById(1);
14    System.out.println(user2);
15    System.out.println(user2.hashCode());
16
17}

6.一对一关联查询

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

1.创建实体类

1public class Student {
    
    
2    private int sid;
3    private String name;
4    private int age;
5    private String sex;
6    private Classes classes;
7    // 省略getter/setter/toString
8}
9
10public class Classes {
    
    
11    private int cid;
12    private String className;
13    private List<Student> students;
14    // 省略getter/setter/toString
15}

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

1public interface StudentMapper {
    
    
2    @Select("select * from student")
3    List<Student> findAll();
4}
5
6public interface ClassesMapper {
    
    
7    // 根据id查询班级
8    @Select("select * from classes where cid = #{cid}")
9    Classes findByCid(Integer cid);
10}

3.主表的查询配置自定义映射关系

1@Select("select * from student")
2// 自定义映射关系
3@Results(id = "studentMapper",value = {
    
    
4    @Result(id = true,property = "sid",column = "sid"),
5    @Result(property = "name",column = "name"),
6    @Result(property = "age",column = "age"),
7    @Result(property = "sex",column = "sex"),
8    /**
9             * property:属性名
10             * column:调用从表方法时传入的参数列
11             * one:表示该属性是一个对象
12             * select:调用的从表方法
13             * fetchType:加载方式
14             */
15    @Result(property = "classes",column = "classId",
16            one = @One(select = "com.package.mapper.ClassesMapper.findByCid",
17                       fetchType = FetchType.EAGER))
18})
19List<Student> findAll();

4.测试

1@Test
2public void findAllStudent(){
    
    
3    StudentMapper studentMapper = session.getMapper(StudentMapper.class);
4    List<Student> all = studentMapper.findAll();
5    all.forEach(System.out::println);
6}

7.一对多关联查询

创建分解后的查询方法

1public interface ClassesMapper {
    
    
2    // 查询所有班级
3    @Select("select * from classes")
4    List<Classes> findAll();
5}
6
7public interface StudentMapper {
    
    
8    // 根据班级id查询学生
9    @Select("select * from student where classId = #{classId}")
10    List<Student> findByClassId(int classId);
11}

主表的查询配置自定义映射关系

1// 查询所有班级
2@Select("select * from classes")
3@Results(id = "classMapper", value = {
    
    
4    @Result(id = true, property = "cid", column = "cid"),
5    @Result(property = "className", column = "className"),
6    // many:表示该属性是一个集合
7    @Result(property = "studentList", column = "cid",
8            many = @Many(select = "com.package.mapper.StudentMapper.findByClassId",
9                         fetchType = FetchType.LAZY))
10})
11List<Classes> findAll();

测试

1@Test
2public void findAllClasses(){
    
    
3    ClassesMapper classesMapper = session.getMapper(ClassesMapper.class);
4    List<Classes> all = classesMapper.findAll();
5    all.forEach(System.out::println);
6}

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

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

映射文件:

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

注解:

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

十、PageHelper分页插件

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

在这里插入图片描述

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

1.引入依赖

1<!-- PageHelper -->
2<dependency>
3    <groupId>com.github.pagehelper</groupId>
4    <artifactId>pagehelper</artifactId>
5    <version>5.3.0</version>
6</dependency>

2.Mybatis配置文件中配置PageHelper插件

1<plugins>
2    <plugin interceptor="com.github.pagehelper.PageInterceptor">
3        <!-- 设置数据库类型-->
4        <property name="helperDialect" value="mysql"/>
5    </plugin>
6</plugins>

3.使用PageHelper插件

1@Test
2public void testFindPage() {
    
    
3    // (1)查询前设置分页参数,参数一:页数,从1开始。参数二:每页条数
4    PageHelper.startPage(1, 3);
5    // (2)正常查询
6    List<User> all = userMapper.findAll();
7    // (3)创建页面对象,创建时将查询结果传入构造方法
8    PageInfo pageInfo = new PageInfo(all);
9    // (4)打印页面对象的属性
10    System.out.println("结果集:"+pageInfo.getList());
11    System.out.println("总条数:"+pageInfo.getTotal());
12    System.out.println("总页数"+pageInfo.getPages());
13    System.out.println("当前页"+pageInfo.getPageNum());
14    System.out.println("每页条数"+pageInfo.getSize());
15}

十一、MyBatis Generator

1.工具引入

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

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

1.准备数据库表

2.在pom文件中配置MBG插件

1<build>
2    <plugins>
3        <plugin>
4            <groupId>org.mybatis.generator</groupId>
5            <artifactId>mybatis-generator-maven-plugin</artifactId>
6            <version>1.3.7</version>
7            <configuration>
8                <!-- MBG配置文件位置 -->
9                        <configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
10                <!-- 运行显示详情 -->
11                <verbose>true</verbose>
12                <!-- 允许覆盖文件 -->
13                <overwrite>true</overwrite>
14            </configuration>
15        </plugin>
16    </plugins>
17</build>

3.编写MBG配置文件

1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE generatorConfiguration
3        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
4        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
5
6<generatorConfiguration>
7    <!-- jdbc的jar包位置,插件需要连接数据库 -->   
8    <classPathEntry location="F:\repository\mysql\mysql-connector-java\8.0.26\mysql-connector-java-8.0.26.jar"/>
9    
10    <context id="default" targetRuntime="MyBatis3">
11        <!-- 是否去除自动生成的注释-->
12        <commentGenerator>
13            <property name="suppressAllComments" value="true"/>
14        </commentGenerator>
15        
16        <!--数据库连接参数-->
17        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
18                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
19                        userId="root"
20                        password="root"></jdbcConnection>
21
22        <!-- 类型处理器,在数据库类型和java类型之间的转换控制-->
23        <javaTypeResolver>
24            <property name="forceBigDecimals" value="false"/>
25        </javaTypeResolver>
26
27        <!-- targetProject:JAVA类路径  targetProject:生成的POJO类的包-->
28        <javaModelGenerator targetProject="src/main/java" targetPackage="com.package.pojo">
29            <!-- 是否生成子包 -->
30            <property name="enableSubPackages" value="false"/>
31            <!-- 设置是否在getter方法中,对String类型字段调用trim()方法 -->
32            <property name="trimStrings" value="true"/>
33        </javaModelGenerator>
34
35        <!-- targetProject:配置文件路径 targetPackage:生成映射文件的位置 -->
36        <sqlMapGenerator targetProject="src/main/resources" targetPackage="com.package.mapper">
37            <!-- 是否生成子包 -->
38            <property name="enableSubPackages" value="false"/>
39        </sqlMapGenerator>
40
41        <!-- targetPackage:JAVA类路径 targetProject:生成的持久层接口包 -->
42        <javaClientGenerator targetProject="src/main/java" targetPackage="com.package.mapper" type="XMLMAPPER">
43            <!-- 是否生成子包 -->
44            <property name="enableSubPackages" value="false"/>
45        </javaClientGenerator>
46
47        <!-- 数据库表,表名不要和其他库中的表名一样 -->
48        <table tableName="product"></table>
49    </context>
50</generatorConfiguration>

4.运行插件,自动生成POJO,持久层接口,映射文件:

在这里插入图片描述

Product.java:POJO类

ProductMapper.java:持久层接口

ProductMapper.xml:映射文件

ProductExample.java:查询扩展类,该类可以构造复杂的查询条件。

Criterion:代表一个字段。
GeneratedCriteria:抽象类,生成查询条件的工具。
Criteria:GeneratedCriteria的子类,生成查询条件的工具。

5.在配置文件中注册生成的映射文件

1<mappers>
2    <mapper class="com.package.mapper.ProductMapper">         </mapper>
3</mappers>

2.增删改方法

1public class TestMBG {
    
    
2    InputStream is = null;
3    SqlSession session = null;
4    ProductMapper productMapper = null;
5
6    @Before
7    public void before() throws IOException {
    
    
8        is = Resources.getResourceAsStream("SqlMapConfig.xml");
9        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
10        SqlSessionFactory factory = builder.build(is);
11        session = factory.openSession();
12        productMapper = session.getMapper(ProductMapper.class);
13    }
14
15    @After
16    public void after() throws IOException {
    
    
17        session.close();
18        is.close();
19    }
20
21    // 新增
22    @Test
23    public void testAdd(){
    
    
24        Product product = new Product("包子", 1.0);
25        productMapper.insert(product);
26        session.commit();
27    }
28    // 修改
29    @Test
30    public void testUpdate(){
    
    
31        Product product = new Product(4,"馒头", 0.8);
32        productMapper.updateByPrimaryKey(product);
33        session.commit();
34    }
35    // 删除
36    @Test
37    public void testDelete(){
    
    
38        productMapper.deleteByPrimaryKey(4);
39        session.commit();
40    }
41
42}

3.查询方法

1// 根据id查询
2@Test
3public void testFindById() {
    
    
4    Product product = productMapper.selectByPrimaryKey(1);
5    System.out.println(product);
6}
7
8// 查询所有
9@Test
10public void testFindAll() {
    
    
11    // 查询扩展对象,可以构建查询条件
12    ProductExample productExample = new ProductExample();
13    List<Product> products = productMapper.selectByExample(productExample);
14    products.forEach(System.out::println);
15}
16
17// 根据商品名查询
18@Test
19public void testFindByName(){
    
    
20    // 查询扩展对象,可以构建查询条件
21    ProductExample productExample = new ProductExample();
22    // 构建查询条件
23    ProductExample.Criteria criteria = productExample.createCriteria();
24    criteria.andProductnameLike("%包%");
25    // 查询
26    List<Product> products = productMapper.selectByExample(productExample);
27    products.forEach(System.out::println);
28}

4.复杂查询方法

1// 多条件and查询
2@Test
3public void testFindAnd() {
    
    
4    // 查询扩展对象,可以构建查询条件
5    ProductExample productExample = new ProductExample();
6    // 构建查询条件
7    ProductExample.Criteria criteria = productExample.createCriteria();
8    criteria.andProductnameLike("%馒%");
9    criteria.andPriceBetween(0.0,233333.3);
10
11    // 查询
12    List<Product> products = productMapper.selectByExample(productExample);
13    products.forEach(System.out::println);
14}
15
16// 多条件or查询
17@Test
18public void testFindOr() {
    
    
19    // 查询扩展对象,可以构建查询条件
20    ProductExample productExample = new ProductExample();
21    // 构建查询条件
22    ProductExample.Criteria criteria = productExample.createCriteria();
23    criteria.andProductnameLike("%头%");
24
25    ProductExample.Criteria criteria1 = productExample.createCriteria();
26    criteria1.andPriceBetween(0.0,1.0);
27
28    productExample.or(criteria1);
29
30    // 查询
31    List<Product> products = productMapper.selectByExample(productExample);
32    products.forEach(System.out::println);
33}

十二、动态代理

1.代理模式简介

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

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

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

2.JDK动态代理

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

1// 被代理接口
2public interface Apple {
    
    
3    String sell(double price);//卖产品
4    void repair();// 维修
5}
6
7// 被代理接口的实现类
8public class AppleImpl implements Apple{
    
    
9    @Override
10    public String sell(double price) {
    
    
11        System.out.println("产品卖了"+price+"元");
12        return "iphone13";
13    }
14
15    @Override
16    public void repair() {
    
    
17        System.out.println("苹果售后维修");
18    }
19}
20
21// 代理方式类,定义被代理方法的增强方式
22// 该类实现InvocationHandler接口,重写invoke方法,定义方法的增强方式
23public class ShoppingProxy implements InvocationHandler {
    
    
24    private Apple apple;// 被代理对象
25    public ShoppingProxy(Apple apple) {
    
    
26        this.apple = apple;
27    }
28
29    /**
30     * 定义原方法的增强方式
31     * @param proxy 被代理对象
32     * @param method 被代理对象调用的方法
33     * @param args 被代理对象调用的方法时,传入的参数
34     * @return 方法的返回值
35     * @throws Throwable
36     */
37    @Override
38    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
39        String name = method.getName(); //被代理对象执行的方法名
40        if("sell".equals(name)){
    
    
41            double price = (double)args[0]*0.9; //增强参数
42            Object result = method.invoke(apple, price); // 执行方法
43            return result + "和一张京剧观看门票"; // 增强返回值
44        }else if("repair".equals(name)){
    
    
45            System.out.println("京东专属客服为您服务!"); // 增强方法流程
46            return method.invoke(apple,args);
47        }else{
    
    
48            return method.invoke(apple,args); // 什么都不增强
49        }
50    }
51}
52
53public class Test {
    
    
54    public static void main(String[] args) {
    
    
55        // 被代理对象
56        Apple apple = new AppleImpl();
57        // 代理方式对象
58        ShoppingProxy shoppingProxy = new ShoppingProxy(apple);
59        // 生成代理对象
60        Apple appleJD = (Apple) Proxy.newProxyInstance(
61                apple.getClass().getClassLoader(), // 类加载器
62                apple.getClass().getInterfaces(),//被代理接口
63                shoppingProxy //代理方式对象
64        );
65        // 执行增强后的方法
66        String sell = appleJD.sell(6000);
67        System.out.println(sell);
68
69        appleJD.repair();
70    }
71}

3.CGLib动态代理

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

1<!-- 引入cglib依赖 -->
2<dependencies>
3    <dependency>
4        <groupId>cglib</groupId>
5        <artifactId>cglib</artifactId>
6        <version>3.3.0</version>
7    </dependency>
8</dependencies>
9    
10// 被代理类
11public class Apple{
    
    
12    public String sell(double price) {
    
    
13        System.out.println("产品卖了"+price+"元");
14        return "iphone13";
15    }
16    public void repair() {
    
    
17        System.out.println("苹果售后维修");
18    }
19}
20
21// 代理方式类,实现MethodInterceptor接口,重写intercept方法
22public class ShoppingProxy implements MethodInterceptor {
    
    
23    private Apple apple; // 被代理对象
24    public ShoppingProxy(Apple apple) {
    
    
25        this.apple = apple;
26    }
27
28    /**
29     * 定义原方法的增强方式
30     * @param o 被代理对象
31     * @param method 被代理对象调用的方法
32     * @param objects 被代理对象调用的方法时,传入的参数
33     * @param methodProxy 底层生成的代理类的引用
34     * @return 方法的返回值
35     * @throws Throwable
36     */
37    @Override
38    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
    
39        String name = method.getName();
40        if("sell".equals(name)){
    
    
41            double price = (double)objects[0]*0.8;
42            Object result = method.invoke(apple, price);
43            return result+"和小熊玩偶";
44        }else if("repair".equals(name)){
    
    
45            System.out.println("淘宝专属客服为您服务!");
46            return method.invoke(apple,objects);
47        }else{
    
    
48            return method.invoke(apple,objects);
49        }
50    }
51}
52
53public class Test {
    
    
54    public static void main(String[] args) {
    
    
55        // 被代理对象
56        Apple apple = new Apple();
57        // 代理方式
58        ShoppingProxy shoppingProxy = new ShoppingProxy(apple);
59        // 生成代理对象
60        Apple appleTB = (Apple) Enhancer.create(Apple.class, shoppingProxy);
61
62        // 执行增强后的方法
63        String sell = appleTB.sell(9000);
64        System.out.println(sell);
65        appleTB.repair();
66    }
67}

猜你喜欢

转载自blog.csdn.net/TIpotencial/article/details/123814293