Mybatis---从入门到深化

192 阅读36分钟

目录

一、什么是框架?

二、什么是ORM框架?

三、什么是MyBatis?

四、MyBatis入门案例

环境搭建

 五、MyBatis核心对象及工作流程

MyBatis核心对象

MyBatis工作流程

六、使用SqlSession操作数据库 

 七、Mapper动态代理原理

获取代理对象

查看代理方式

八、MyBatis增删改查

 8.1 新增用户

8.2 修改用户

优化测试类

 8.3 用户删除、根据Id查询

删除用户

 8.4 模糊查询

8.5 分页查询

顺序传参

 8.6 @Param传参

 8.7 POJO传参

8.8 Map传参

 9、聚合查询、主键回填

查询用户总数

 主键回填

 10、配置文件

10.1 配置文件_

 10.2 配置文件_

10.3 配置文件_

10.4 配置文件_

10.5 MyBatis配置文件_

 事务管理

连接池

10.6 MyBatis配置文件_ 

 10.7 MyBatis映射文件_

resultMap

 10.8 MyBatis映射文件_、

 10.9 MyBatis映射文件_特殊字符处理

 11  MyBatis动态

11.1 动态SQL_

11.2 动态SQL_ 

11.3 动态SQL_

 11.4 动态SQL_

11.5 动态SQL_

遍历数组

 遍历Collection

 遍历Map

 12  MyBatis缓存

缓存介绍

 MyBatis一级缓存

测试一级缓存 

MyBatis清空一级缓存

MyBatis二级缓存

开启二级缓存 

测试二级缓存 

13、MyBatis关联查询

MyBatis一对一关联查询 

MyBatis一对多关联查询

MyBatis多对多关联查询

14、MyBatis分解式查询

14.1、MyBatis分解式查询_一对多

14.2、MyBatis分解式查询_一对一

14.3、MyBatis分解式_延迟加载

15、MyBatis注解开发

15.1、MyBatis注解开发_环境搭建 

15.2 MyBatis注解开发_增删改查

15.3、 MyBatis注解开发_动态Sql

使用脚本标签

15.4、MyBatis注解开发_自定义映射关系

 15.5、MyBatis注解开发_二级缓存

 15.6、MyBatis注解开发_一对一关联查询

 15.7、MyBatis注解开发_一对多关联查询

 15.8、注解开发与映射文件开发的对比

16.MyBatis分页插件 

16.1、PageHelper分页插件

17、MyBatisGenerator_工具引入(1)

 18、MyBatisGenerator_工具引入(2)

19、MyBatisGenerator_增删改方法

20、MyBatis Generator_查询方法

21、MyBatis Generator_复杂查询

22、动态代理_代理模式简介

23、动态代理_JDK动态代理 

 24、动态代理_CGLib动态代理


一、什么是框架?

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

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

使用框架开发的好处:

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

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

二、什么是ORM框架?

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

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

// 新增方法,将对象转为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框架就会根据映射完成对数据库的操作,极大的增强了开发效率。

三、什么是MyBatis?

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入门案例

环境搭建

  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.10</version>
      </dependency>
      <!--  log4j  -->
      <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
      </dependency>
    </dependencies>
    
  3.  创建mybatis核心配置文件SqlMapConfig.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
      <!--  配置环境  -->
      <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="root"/>
          </dataSource>
        </environment>
      </environments>
    </configuration>
    
  4. 将log4j.properties文件放入resources中,让控制台打印SQL语句。

  5. 创建实体类

    public class User {
      private int id;
      private String username;
      private String sex;
      private String address;
        // 省略getter/setter/构造方法/toString方法
    }
    
  6. 创建持久层接口和映射文件 

 6.1 在java目录创建持久层接口

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

6.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.itbaizhan.mapper.UserMapper">
  <select id="findAll" resultType="com.itbaizhan.pojo.User">
     select * from user
  </select>
</mapper>

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

<!--  注册映射文件  -->
<mappers>
  <mapper resource="com/itbaizhan/mapper/UserMapper.xml">     </mapper>
</mappers>

映射文件注意事项:

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

  7. 测试持久层接口方法 

@Test
public void testFindAll() throws Exception {
  // (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 userMapper = session.getMapper(UserMapper.class);
  // (6)代理对象执行方法
  List<User> all = userMapper.findAll();
  all.forEach(System.out::println);

  // (7)释放资源
  session.close();
  is.close();
}

 五、MyBatis核心对象及工作流程

MyBatis核心对象

  • SqlSessionFactoryBuilder

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

  • SqlSessionFactory

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

  • SqlSession

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

  • Mapper

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

MyBatis工作流程

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

六、使用SqlSession操作数据库 

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

@Test
public void testFindAll2() throws Exception {
  // (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> users = session.selectList("com.itbaizhan.mapper.UserMapper.findAll");
  users.forEach(System.out::println);
  // (6)关闭资源
  session.close();
  is.close();
}

 七、Mapper动态代理原理

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

获取代理对象

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

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

查看代理方式

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

 

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

结论:

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

八、MyBatis增删改查

 8.1 新增用户

8.1.1 持久层接口添加方法

void add(User user);

8.1.2 映射文件添加标签

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

8.1.3 编写测试方法

@Test
public void testAdd() throws Exception {
  InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  SqlSession session = factory.openSession();
  UserMapper userMapper = session.getMapper(UserMapper.class);
  User user = new User("程序员", "男", "上海");
  userMapper.add(user);
  // 提交事务
  session.commit();
  session.close();
  is.close();
}

注意:

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

8.2 修改用户

优化测试类

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

InputStream is = null;
SqlSession session = null;
UserMapper userMapper = 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)获取代理对象
  userMapper = session.getMapper(UserMapper.class);
}

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

持久层接口添加方法

void update(User user);

 映射文件添加标签

<update id="update" parameterType="com.itbaizhan.pojo.User">
   update user
   set username = #{username},
   sex    = #{sex},
   address=#{address}
   where id = #{id}
</update>

 编写测试方法

@Test
public void testUpdate(){
  User user = new User(8,"程序员1","女","深圳");
  userMapper.update(user);
  session.commit();
}

 8.3 用户删除、根据Id查询

删除用户

持久层接口添加方法

void delete(int userId);

 映射文件添加标签

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

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

  • 简单数据类型:基本数据类型、字符串等

编写测试方法 

@Test
public void testDelete(){
  userMapper.delete(8);
  session.commit();
}

根据ID查询用户

持久层接口添加方法

User findById(int userId);

 映射文件添加标签

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

 编写测试方法

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

 8.4 模糊查询

8.4.1 使用#定义参数

持久层接口添加方法

List<User> findByNameLike(String username);

 映射文件添加标签

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

 编写测试方法

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

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

  • 参数/返回值类型为基本数据类型/包装类/String等类型时,我们可以写全类名,也可以写别名。
数据类型别名
byte_byte
long_long
short_short
int_int
int_integer
double_double
float_float
boolean_boolean
Stringstring
Bytebyte
Longlong
Shortshort
Integerint/integer
Doubledouble
Floatfloat
Booleanboolean
Datedate
BigDecimaldecimal/bigdecimal
Objectobject
Mapmap
HashMaphashmap
Listlist
ArrayListarraylist
Collectioncollection
Iteratoriterator

8.4.2 使用$定义参数

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

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

测试方法写法如下:

@Test
public void testFindByNameLike(){
  List<User> users = userMapper.findByUsernameLike("程序");
  users.forEach(System.out::println);
}

#和$的区别:

  1. #表示sql模板的占位符,$表示将字符串拼接到sql模板中。
  2. #可以防止sql注入,一般能用#就不用$。
  3. ${}内部的参数名必须写value。

8.4.3 使用<bind>定义参数

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

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

测试方法写法如下:

@Test
public void testFindByNameLike(){
  List<User> users = userMapper.findByUsernameLike("程序");
  users.forEach(System.out::println);
}

8.5 分页查询

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

顺序传参

Sql中的参数使用arg0,arg1...或param1,param2...表示参数的顺序。此方法可读性较低,在开发中不建议使用。

持久层接口方法

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

 映射文件

<select id="findPage" resultType="com.itbaizhan.mapper.User">
   select * from user limit #{arg0},#{arg1}
</select>

<select id="findPage" resultType="com.itbaizhan.mapper.User">
   select * from user limit #{param1},#{param2}
</select>

 测试类

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

8.6 @Param传参

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

持久层接口方法

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

 映射文件

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

 测试类

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

 8.7 POJO传参

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

自定义POJO

public class PageQuery {
  private int startIndex;
  private int pageSize;
    // 省略getter/setter/构造方法
}

持久层接口方法

List<User> findPage2(PageQuery pageQuery);

 映射文件

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

 测试类

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

8.8 Map传参

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

持久层接口方法

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

 映射文件

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

 测试类

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

 9、聚合查询、主键回填

查询用户总数

持久层接口方法

int findCount();

 映射文件

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

 测试类

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

主键回填

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

持久层接口方法

void add(User user);

映射文件

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

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

测试类 

@Test
public void testAdd(){
  User user = new User("尚学堂", new Date(), "男", "北京");
  userMapper.add(user);
  session.commit();
  System.out.println(user.getId());
}

 10、配置文件

10.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标签引入外部配置文件,这样可以做到动态配置数据源。

编写db.properties 

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password01=root

 在配置文件中引入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.driver" value="com.mysql.jdbc.Driver"></property>
  <property name="jdbc.url" value="jdbc:mysql://localhost:3306/mybatis"></property>
  <property name="jdbc.username" value="root"></property>
  <property name="jdbc.password" value="root"></property>
</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>

 10.2 配置文件_<settings>

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

10.3 配置文件_<plugins>

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

10.4 配置文件_<typeAliases>

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

为一个类配置别名

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

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

配置文件:

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

 映射文件

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

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

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

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

配置文件:

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

 映射文件:

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

10.5 MyBatis配置文件_<environments>

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

 事务管理

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

连接池

<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自带的连接池管理。

10.6 MyBatis配置文件_<mappers> 

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

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

<mappers>
 <mapper resource="com/itbaizhan/mapper/UserMapper.xml"/>
</mappers>

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

<mappers>  
  <mapper url="file:///C:\Users\a\IdeaProjects\mybatiscase\mybatisDemo1\src\main\resources\com\itbaizhan\mapper\UserMapper.xml"/> 
</mappers>

 注册持久层接口

<mappers>  
  <mapper class="com.itbaizhan.mapper.UserMapper"/> 
</mappers>

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

<mappers>
  <package name="com.itbaizhan.mapper"/>
</mappers>

 10.7 MyBatis映射文件_<resultMap>

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

resultMap

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

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

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

 此时有两种解决方案:

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

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

自定义映射关系

  • 在映射文件中,使用<resultMap>自定义映射关系:
<!-- id:自定义映射名 type:自定义映射的对象类型  -->
<resultMap id="teacherMapper" type="com.itbaizhan.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>

 10.8 MyBatis映射文件_<sql>、<include>

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

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

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

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

 10.9 MyBatis映射文件_特殊字符处理

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

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

符号实体
<<
>
&&
'
"
<select id="findById2" resultType="com.itbaizhan.pojo.Teacher">
  <include refid="selectAllField"></include>
   from teacher where tid &gt; #{id}
</select>

 11  MyBatis动态

11.1 动态SQL_<if>

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

<if>

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

持久层接口添加方法 

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

 映射文件添加标签

<select id="findByCondition" parameterType="com.itbaizhan.pojo.User" resultType="com.itbaizhan.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>

 编写测试方法

@Test
public void testFindByCondition(){
  User user = new User();
  List<User> users1 = userMapper2.findByCondition(user);
  //users1.forEach(System.out::println);

  user.setUsername("%尚学堂%");
  List<User> users2 = userMapper2.findByCondition(user);
  users2.forEach(System.out::println);

  user.setAddress("北京");
  List<User> users3 = userMapper2.findByCondition(user);
  users3.forEach(System.out::println);
}
  1. if中的条件不能使用&&/||,而应该使用and/or

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

  3. where后为什么要加1=1?

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

11.2 动态SQL_<where> 

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

<select id="findByCondition" resultType="com.itbaizhan.user.User" parameterType="com.itbaizhan.user.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>
  </where>
</select>

11.3 动态SQL_<set>

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

<update id="update" parameterType="com.itbaizhan.user.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>
  </set>
  <where>
     id = #{id}
  </where>
</update>

 11.4 动态SQL_<choose>、<when>、<otherwise>

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

<select id="findByCondition" resultType="com.itbaizhan.user.User" parameterType="com.itbaizhan.user.User">
   select * from user
  <where>
    <choose>
      <when test="username.length() &lt; 5">
         username like #{username}
      </when>
      <when test="username.length() &lt; 10">
         username = #{username}
      </when>
      <otherwise>
         id = 1
      </otherwise>
    </choose>
  </where>
</select>

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

11.5 动态SQL_<foreach>

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

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

遍历数组

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

持久层接口添加方法

void deleteBatch(int[] ids);

 映射文件添加标签

<delete id="deleteBatch" parameterType="int">
   delete from user
  <where>
    <foreach open="id in(" close=")" separator="," collection="array" item="id" >
       #{id}
    </foreach>
  </where>
</delete>

 编写测试方法

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

 遍历Collection

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

持久层接口添加方法

void insertBatch(List<User> users);

 映射文件添加标签

<insert id="insertBatch" parameterType="com.itbaizhan.user.User">
   insert into user values
  <foreach collection="list" item="user" separator=",">
    #{user.username},#{user.birthday},#{user.sex},#{user.address})
  </foreach>
</insert>

 编写测试方法

@Test
    public void testInsertBatch(){
        User user1 = new User("程序员1", "男", "北京");
        User user2 = new User("程序员2", "女", "上海");
        List<User> users = new ArrayList();
        users.add(user1);
        users.add(user2);

        userMapper2.insertBatch(users);
        session.commit();
    }

 遍历Map

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

 持久层接口添加方法

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

 映射文件添加标签

<select id="findUser" parameterType="map" resultType="com.itbaizhan.pojo.User">
   select * from user
  <where>
    <foreach collection="queryMap" separator="and" index="key" item="value">
       ${key} = #{value}
    </foreach>
  </where>
</select>

 编写测试方法

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

 12  MyBatis缓存

缓存介绍

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

  • 什么是缓存?

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

  • 缓存有什么作用?

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

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

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

  • MyBatis缓存分为哪几类?

    一级缓存和二级缓存

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

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

 MyBatis一级缓存

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

测试一级缓存 

//同一个SqlSession查询
@Test
public void testCache1() throws IOException {
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  SqlSession session = factory.openSession();

  // 使用同一个SqlSession查询
  UserMapper mapper1 = session.getMapper(UserMapper.class);
  UserMapper mapper2 = session.getMapper(UserMapper.class);

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

//不同的SqlSession查询
@Test
public void testCache2() throws IOException {
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  SqlSession session1 = factory.openSession();
  SqlSession session2 = factory.openSession();

  // 使用不同的SqlSession查询
  UserMapper mapper1 = session1.getMapper(UserMapper.class);
  UserMapper mapper2 = session2.getMapper(UserMapper.class);

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

同一个SqlSession查询

不同的SqlSession查询 

MyBatis清空一级缓存

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

SqlSession调用close():操作后SqlSession对象不可用,该对象的缓存数据也不可用。
SqlSession调用clearCache()/commit():操作会清空一级缓存数据。
SqlSession调用增删改方法:操作会清空一级缓存数据,因为增删改后数据库发生改变,缓存数据将不准确。
@Test
public void testCache3() throws IOException {
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  SqlSession session = factory.openSession();

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

  User user1 = mapper1.findById(1);
  System.out.println(user1.hashCode());
  //     session.close();
  //     session.clearCache();
  //     session.commit();
  mapper1.delete(2);
  System.out.println("-------------------------------------------");
  User user2 = mapper2.findById(1);
  System.out.println(user2.hashCode());
}

MyBatis二级缓存

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

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

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

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

开启二级缓存

POJO类实现Serializable接口。

public class User implements Serializable {
  private int id;
  private String username;
  private String sex;
  private String address;
}

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

//SqlMapConfig.xml中
<settings>
  <setting name="cacheEnabled" value="true"/>
</settings>

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

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

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

//UserMapper.xml中
<cache size="2048"/>

测试二级缓存 

@Test
public void testCache4() throws IOException {
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  SqlSession session1 = factory.openSession();
  SqlSession session2 = factory.openSession();

  UserMapper mapper1 = session1.getMapper(UserMapper.class);
  UserMapper mapper2 = session2.getMapper(UserMapper.class);

  User user1 = mapper1.findById(1);
  System.out.println(user1);
  System.out.println(user1.hashCode());
  // 让一级缓存失效
  session1.commit();
  System.out.println("-------------------------------------------");

  User user2 = mapper2.findById(1);
  System.out.println(user2);
  System.out.println(user2.hashCode());
}

13、MyBatis关联查询

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
}

 数据库设计如下:

MyBatis一对一关联查询 

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

创建持久层接口

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

创建映射文件

<resultMap id="studentMapper" type="com.itbaizhan.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.itbaizhan.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>

配置文件注册映射文件

<mappers>
  <package name="com.itbaizhan.mapper"/>
</mappers>

测试一对一关联查询

InputStream is = null;
SqlSession session = null;

@Before
public void before() throws IOException {
  is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  session = factory.openSession();
}

@After
public void after() throws IOException {
  session.close();
  is.close();
}

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

MyBatis一对多关联查询

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

创建持久层接口

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

创建映射文件

<resultMap id="classesMapper" type="com.itbaizhan.pojo.Classes">
  <id property="cid" column="cid"></id>
  <result property="className" column="className"></result>
  <!-- 集合列  property:属性名  column:关联列名 ofType:集合的泛型 -->
  <collection property="studentList" column="classId" ofType="com.itbaizhan.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>

测试一对多关联查询

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

MyBatis多对多关联查询

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
}

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

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

创建持久层接口

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

创建映射文件

<resultMap id="teacherMapper" type="com.itbaizhan.pojo.Teacher">
  <id column="tid" property="tid"></id>
  <result column="tname" property="tname"></result>
  <collection property="classes" column="tid" ofType="com.itbaizhan.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>

测试多对多关联查询

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

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

<resultMap id="classesMapper" type="com.itbaizhan.pojo.Classes">
  <id property="cid" column="cid"></id>
  <result property="className" column="className"></result>
  <!-- 集合列  property:属性名  column:关联列名 ofType:集合的泛型 -->
  <collection property="studentList" column="classId" ofType="com.itbaizhan.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="teacherList" column="cid" ofType="com.itbaizhan.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 classes_teacher.tid = teacher.tid;
</select>

14、MyBatis分解式查询

14.1、MyBatis分解式查询_一对多

 在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查询:

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

public interface ClassesMapper {
  // 查询所有班级
  List<Classes> findAll();
}

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

在映射文件中进行配置

<select id="findAll" resultType="com.itbaizhan.pojo.Classes">
   select * from classes
</select>

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

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

<!-- 自定义映射关系  -->
<resultMap id="MyClassesMapper" type="com.itbaizhan.pojo.Classes">
  <id property="cid" column="cid"></id>
  <result property="className" column="className"></result>
  <!-- select:从表查询调用的方法  column:调用方法时传入的参数字段   -->
  <collection property="studentList"
        ofType="com.itbaizhan.pojo.Student"        select="com.itbaizhan.mapper2.StudentMapper2.findByClassId"
        column="cid">
  </collection>
</resultMap>

<select id="findAll" resultMap="MyClassesMapper">
   select * from classes
</select>

测试查询方法

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

14.2、MyBatis分解式查询_一对一

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

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

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

public interface StudentMapper {
   // 查询所有学生
  List<Student> findAll();
}

public interface ClassesMapper {
  // 根据ID查询班级
  Classes findByCid(int cid);
}

在映射文件中进行配置

<select id="findAll" resultType="com.itbaizhan.pojo.Student">
   select *
   from student
</select>

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

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

<resultMap id="MyStudentMapper" type="com.itbaizhan.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.itbaizhan.pojo.Classes"
         select="com.itbaizhan.mapper2.ClassesMapper2.findByCid"
         column="classId">
  </association>
</resultMap>

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

测试查询方法

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

14.3、MyBatis分解式_延迟加载

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

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

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

开启延迟加载

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

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

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

测试延迟加载

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

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

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

<settings>
  <setting name="lazyLoadTriggerMethods" value=""/>
</settings>

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

15、MyBatis注解开发

15.1、MyBatis注解开发_环境搭建

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();
}

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

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

<mappers>
  <package name="com.itbaizhan.mapper"/>
</mappers>

 测试方法

InputStream is = null;
SqlSession session = null;
UserMapper userMapper = null;

@Before
public void before() throws IOException {
  is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  session = factory.openSession();
  userMapper = session.getMapper(UserMapper.class);
}

@After
public void after() throws IOException {
  session.close();
  is.close();
}


@Test
public void testFindAll(){
  List<User> all = userMapper.findAll();
  all.forEach(System.out::println);
}

15.2 MyBatis注解开发_增删改查

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

@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);

15.3、 MyBatis注解开发_动态Sql

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

使用脚本标签

将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);

在方法中构建动态Sql

在MyBatis中有@SelectProvider@UpdateProvider@DeleteProvider@InsertProvider注解。
当使用这些注解时将不在注解中直接编写SQL,而是调用某个类的方法来生成SQL
// 生成根据任意条件查询的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();
}

15.4、MyBatis注解开发_自定义映射关系

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

// 查询所有用户
@Results(id = "userDiyMapper" ,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();

// 根据id查询
@ResultMap("userDiyMapper")
@Select("select * from user where id = #{id}")
User findById(int id);

 15.5、MyBatis注解开发_二级缓存

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

  1. POJO类实现Serializable接口。
  2. 在MyBatis配置文件添加如下设置:
<settings>
  <setting name="cacheEnabled" value="true"/>
</settings>
  1. 在持久层接口上方加注解@CacheNamespace(blocking=true),该接口的所有方法都支持二级缓存。
  2. 测试二级缓存
@Test
public void testCache() throws IOException {
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  SqlSession session1 = factory.openSession();
  SqlSession session2 = factory.openSession();

  User user1 = session1.getMapper(UserMapper.class).findById(1);
  System.out.println(user1);
  System.out.println(user1.hashCode());
  session1.commit(); // 清空一次缓存,将数据存到二级缓存
  User user2 = session2.getMapper(UserMapper.class).findById(1);
  System.out.println(user2);
  System.out.println(user2.hashCode());

}

 15.6、MyBatis注解开发_一对一关联查询

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

  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
}

 创建分解后的查询方法

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);
}

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

@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.itbaizhan.mapper.ClassesMapper.findByCid",
            fetchType = FetchType.EAGER))
})
List<Student> findAll();

 测试

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

 15.7、MyBatis注解开发_一对多关联查询

创建分解后的查询方法

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);
}

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

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

 测试

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

 15.8、注解开发与映射文件开发的对比

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

映射文件:

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

注解:

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

16.MyBatis分页插件 

16.1、PageHelper分页插件

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

 

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

  1. 引入依赖
<!-- PageHelper -->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.3.0</version>
</dependency>

 Mybatis配置文件中配置PageHelper插件

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

 使用PageHelper插件

@Test
public void testFindPage() {
  // (1)查询前设置分页参数,参数一:页数,从1开始。参数二:每页条数
  PageHelper.startPage(1, 3);
  // (2)正常查询
  List<User> all = userMapper.findAll();
  // (3)创建页面对象,创建时将查询结果传入构造方法
  PageInfo pageInfo = new PageInfo(all);
  // (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());
}

17、MyBatisGenerator_工具引入(1)

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

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

  1. 准备数据库表 

在pom文件中配置MBG插件

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

编写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="F:\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="root"></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>

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

 

  • Product.java:POJO类

  • ProductMapper.java:持久层接口

  • ProductMapper.xml:映射文件

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

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

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

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

 18、MyBatisGenerator_工具引入(2)

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

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

  1. 准备数据库表

在pom文件中配置MBG插件

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

 编写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="F:\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="root"></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>

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

  • Product.java:POJO类

  • ProductMapper.java:持久层接口

  • ProductMapper.xml:映射文件

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

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

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

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

19、MyBatisGenerator_增删改方法

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

  @Before
  public void before() throws IOException {
    is = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(is);
    session = factory.openSession();
    productMapper = 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);
    productMapper.insert(product);
    session.commit();
   }
  // 修改
  @Test
  public void testUpdate(){
    Product product = new Product(5,"百Python", 25000.0);
    productMapper.updateByPrimaryKey(product);
    session.commit();
   }
  // 删除
  @Test
  public void testDelete(){
    productMapper.deleteByPrimaryKey(5);
    session.commit();
   }

}

20、MyBatis Generator_查询方法

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

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

21、MyBatis Generator_复杂查询

// 多条件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 = productMapper.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 = productMapper.selectByExample(productExample);
  products.forEach(System.out::println);
}

22、动态代理_代理模式简介

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

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

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

23、动态代理_JDK动态代理 

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

// 被代理接口
public interface Apple {
  String sell(double price);//卖产品
  void repair();// 维修
}

// 被代理接口的实现类
public class AppleImpl implements Apple{
  @Override
  public String sell(double price) {
    System.out.println("产品卖了"+price+"元");
    return "iphone13";
   }

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

// 代理方式类,定义被代理方法的增强方式
// 该类实现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); // 什么都不增强
     }
   }
}

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();
   }
}

 24、动态代理_CGLib动态代理

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

<!-- 引入cglib依赖 -->
<dependencies>
  <dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
  </dependency>
</dependencies>
  
// 被代理类
public class Apple{
  public String sell(double price) {
    System.out.println("产品卖了"+price+"元");
    return "iphone13";
   }
  public void repair() {
    System.out.println("苹果售后维修");
   }
}

// 代理方式类,实现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);
     }
   }
}

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();
   }
}