Mybatis知识汇总


目录
1 JDBC
2 Mybaits概述
3 Mybatis扩展
4 Mybatis的加载和缓存
5 Mybatis注解开发
6 Mybatis分页
7 常见问题汇总

参考资料:

  • 《Java使用教程 第3版》
  • JavaG
  • CSDN

JDBC

JDBC是Java DataBase Connectivity的缩写,它是一种可用于执行SQL语句的Java API,其中包含跨平台的数据库访问方法,为数据库应用开发人员提供了一种标准的应用程序编程接口,屏蔽了具体数据库的差异,如图1-1所示。当Java程序访问数据库时,由 JDBC API接口调用相应数据库的API实现来访问数据库,从而无须改变Java程序就能访问不同的数据库。

img

JDBC实现

不同的数据库提供不同的JDBC实现,如图1-2所示,JDBC的实现包括三部分。

img
  • JDBC驱动管理器:对应 java. sql DriverManager类,它负责注册特定JBC驱动器,以及根据驱动器建立与数据库的连接

  • JDBC驱动器API:其中最主要的是java. gl. Driver接口

  • JDBC驱动器:由数据库供应商或其他第三方提供,也称为JDBC驱动程序。它们实现了JDBC驱动器API( Driver接口),负责与特定的数据库连接。JDBC驱动器可以注册到JBC驱动管理器中。不同数据库提供的JDBC驱动器也不同。

访问数据库步骤

(1) 加载驱动程序。调用DriverManager类的registerDriver()方法用来注册驱动程序类的实例。

(2) 建立连接。调用DrierManager类的getConnection()方法得到一个与数据库的连接,返回一个Connection对象。

(3) 操作数据库。 调用Connection对象的createStatement()、prepareStatement()等方法执行SQL语句,返回结果集ResultSet。

(4) 断开连接。

常用接口

Statement接口

调用 Connection对象的 createStatement()方法创建一个 Statement对象。 Statement接口的常用方法如下:

(1) boolean execute(String sql) throws SQLExceptior:执行给定的SoL语句

(2) int executeUpdate(String sql) throws SQLException:执行给定SQL语句,该语句可能为INSERT、 UPDATE或 DELETE语句,或者不返回任何内容的SQL语句(如 SQL DDL语句)

(3) Resultset execute Query( String sql) throws SQLException:执行给定的SQL语句,该语句返回单个 Resultset对象。

(4) void addBatch( (String sql) throws SQLException:将给定的SOL命令添加到此Statement对象的当前命令列表中。通过调用方法 executeBatch可以批量执行此列表中的命令

(5) int[] executeBatch() throws SQLException:将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。

PreparedStatement接口

Java提供了一个 Statement接口的子接口 PreparedStatement,两者的功能相似,但当某SQL指令被执行多次时, PreparedStatement的效率要比 Statement高,而且 PreparedStatement还可以给SQL指令传递参数。调用 Connection对象的 prepareStatement()方法来得到 PreparedStatement对象。PreparedStatement对象所代表的SQL语句中的参数用问号(?)来表示。调用 PreparedStatement对象的 setXXX()方法来设置这些参数。PreparedStatement接口的常用方法如下:

(1) void set Boolean(int parameterIndex, boolean x) throws SQLException:将指定参数设置为给定boolean值。parameterIndex的第一个参数是1,第二个参数是2… …x 是参数值。

(2) void setInt(int parameterIndex, int x) throws SQLException::将指定参数设置为给定int值。

(3) void setFloat(int parameterIndex., float x) throws SQLException:将指定参数设置为给定float值。

(4) void set Double(int parameterIndex, double x) throws SQLException:将指定参数设置为给定double值。

(5) void setString(int parameterIndex, String x) throws SQLException:将指定参数设置为给定String值。

(6) void setDate(int parameterIndex, Date x) throws SQLException:使用运行应用程序的虚拟机的默认时区将指定参数设置为给定java.sql.Date值。

ResultSet接口

当使用 Statement和 PreparedStatement中的 executeQuery方法来执行 select查询指令时,查询的结果被放在结果集 Resultset中。Resultset接口的常用方法如下:

(1) String getString(int columnIndex) throws SQLException:获取此 Resultset对象的当前行中指定列的值,参数 columnIndex代表字段的索引位置。

(2) String getString(String columnLabel) throws SQLException:获取此 Resultset对象的当前行中指定列的值,参数 columnLabel代表字段值。

(3) int getInt (int columnIndex) throws SQLException:获取此 ResultSet对象的当前行中指定列的值,参数 columnIndey代表字段值。

(4) int getInt(String column Label) throws SQLException:获取此 Resultset对象的当前行中指定列的值,参数 columnLabel代表字段值。

(5) boolean absolute(int row) throws SQLException:将光标移动到此 Resultset对象的给定行编号。

(6) boolean previous() throws SQLException:将光标移动到此 Resultset对象的上一行。

(7) boolean first() throws SQLException:将光标移动到此 Resultset对象的第一行。

(8) boolean last() throws SQLException:将光标移动到此 Resultset对象的最后一行。

(9) boolean next() throws SQLException:将光标移到下一行, Resultset光标最初位于第一行之前,第一次调用next0方法使第一行成为当前行。

JDBC Demo

String username = "username=' OR 1=1 -- ";

String password = "12345";

// String sql = "SELECT id,username FROM user_table WHERE " +

// "username='" + username + "'AND " + "password='"

// + password + "'";

//优点在于,可以进行预编译

String sql = "SELECT id,username FROM user_table WHERE username=? AND password=?";

Class.forName("com.mysql.jdbc.Driver");

Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");

PreparedStatement stat = con.prepareStatement(sql);

stat.setString(1, username);

stat.setString(2, password);

System.out.println(stat.toString());

ResultSet rs = stat.executeQuery();

while (rs.next()) {

  String id = rs.getString(1);

  String name = rs.getString(2);

System.out.println("id:" + id + "---name:" + name);

}

事务处理

事务处理是由单一的逻辑单位完成的一系列操作,它由一系列对数据库的操作组成。事务处理在数据库系统中主要用来实现数据完整性,所有遵守JDBC规范的JDBC驱动程序都支持事务处理。当在一个事务中执行多个操作时,只有所有操作成功才意味着整个事务成功。只要有一个操作失败,整个事务就失败,该事务会回滚( rollback)到最初的状态。

当一个连接对象被创建时,默认情况下事务被设置为自动提交状态。这意味着每次执行一条SQL语句时,如果执行成功,就会自动调用 commit()方法向数据库提交,也就不能再回滚了。为了将多条SQL语句作为一个事务执行,可以**设置 Connection对象的 setAutoCommit(false)**。然后在所有的SQL语句成功执行后,显式调用 Connection对象的commit()方法来提交事务,或者在执行出错时调用Connection对象的 rollback()方法来回滚事务。

Mybatis概述

简介

  • Mybatis 是一个使用java编写的持久层框架。它封装了 JDBC ,使开发者只需要关注 sql 语句,而无需关注注册驱动、创建连接、创建 Statement 等繁杂的过程。

  • ORM(Object Relational Mapping)对象关系映射。简单地说,就是把数据库表和实体类及实体类的属性对应起来,让我们可以通过操作实体类来操作数据库表。

maven依赖

mybatis

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->  
<dependency>      
  <groupId>org.mybatis</groupId>      
  <artifactId>mybatis</artifactId>     
  <version>3.4.4</version>
</dependency> 
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> 
<dependency>     
  <groupId>mysql</groupId>     
  <artifactId>mysql-connector-java</artifactId>     
  <version>5.1.21</version> 
</dependency>

相应定义的配置文件:

  • UserMapper.xml
<?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="cn.ykf.mapper.UserMapper">
   <!-- 配置查询所有用户 -->
  <select id="listAllUsers" resultType="cn.ykf.pojo.User">
    SELECT * FROM user
   </select>
</mapper>
  • mybatis.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>
  <!-- 全局变量 -->
  <properties>
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/db_mybatis"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
  </properties>
  <!--配置环境-->
  <environments default="development">
    <environment id="development">
      <!-- 配置事务类型 -->
      <transactionManager type="JDBC"></transactionManager>
      <!-- 配置数据源(连接池) -->
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
   </environments>
   <!-- 指定映射文件 -->
  <mappers>
    <mapper resource="UserMapper.xml"/>
   </mappers>
 </configuration>
  • 注意事项

  • sql中的#{username}等必须与对应类的属性名一致

    • 映射配置文件的 mapper 标签
    • 映射配置文件的操作配置,id 属性的取值必须是 mapper 接口的方法名

springboot整合mybatis

<parent>    
  <groupId>org.springframework.boot</groupId>     
  <artifactId>spring-boot-starter-parent</artifactId>     
  <version>1.5.2.RELEASE</version>     
  <relativePath /> <!-- lookup parent from repository --> 
</parent> 
<dependencies>     
  <dependency>         
  	<groupId>org.springframework.boot</groupId>         
  	<artifactId>spring-boot-starter</artifactId>     
  </dependency>     
  <dependency>         
  	<groupId>org.mybatis.spring.boot</groupId>         
  	<artifactId>mybatis-spring-boot-starter</artifactId>         
    <version>1.1.1</version>     
  </dependency>     
  <dependency>         
  <groupId>mysql</groupId>         
    <artifactId>mysql-connector-java</artifactId>         
    <version>5.1.21</version>     
  </dependency> 
 </dependencies>

原理分析

以mybatis框架情况为例讲解。

mybatis调用的主类

public class MybatisTest {
  /**
  \* Mybatis 入门案例
  */
  @Test
  public void testInit() {
    try {
      // 1. 读取配置文件
      InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
     // 2. 创建 SqlSessionFactory 工厂
     SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
     SqlSessionFactory factory = builder.build(is);
      // 3. 获取 SqlSession 对象
     SqlSession sqlSession = factory.openSession();
     // 4. 使用 SqlSession 创建 Mapper 的代理对象
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     // 5. 使用代理对象执行查询
     List<User users> = mapper.findAll();
     users.forEach(System.out::println);
     // 6. 释放资源
     sqlSession.close();
     is.close();
   } catch (IOException e) {
     e.printStackTrace();
   }
  }
}

img

  • 构建者模式:创建SqlSessionFactory对象
  • 工厂模式: 创建SqlSession对象
  • 代理模式: 创建Dao接口实体类。getMapper(类名.class)方法,使用JDK动态代理创建一个代理对象,在对象的InvocationHandler接口实现类中调用findAll()方法。最后使用反射技术封装实体类返回代理对象。

源码分析

img

在通过代理模式生成代理对象后,调用方法如listAllUsers()的过程如下:
(1)通过dom4j解析xml文件。根据mysql数据库的配置信息创建出Connection对象,注册驱动,建立连接。
(2)获取预处理对象PreparedStatement。将UserMapper.xml文件中对应方法的sql语句传入,调用connection.prepareStatement(sql)获得PreparedStatement对象。
(3)执行查询。ResultSet resultSet = prepareStatement.executeQuery()
(4)遍历结果集,封装为List<User>返回。

· 注意事项
在代理对象调用方法时,mybatis会将方法需要的sql语句和封装结果的实体类全限定类名(即一个标签<select>)进行组合,定义成一个MappedStatement对象。

mybatis扩展

Usermapper.xml中的扩展

  • POJO包装
    pojo是普通JavaBean,即属性+ get/set方法。对应还有EJB是企业JavaBean,是分布式事务处理组件。
    在开发中如果想实现复杂查询 ,查询条件不仅包括用户查询条件,还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用 pojo 包装对象传递输入参数。即新定义一个Bean,包含对其他类的引用。

  • 实体类属性与数据库表不一致

    • 使用别名

      SELECT id AS userId, username AS userName, birthday AS userBirthday, sex AS userSex, address AS userAddress FROM user
    • 使用ResultMap

      <resultMap id="userMap"type="cn.ykf.pojo.User"> 
         <id property="userId" column="id"/>   
         <result property="userName" column="username"/>   
         <result property="userBirthday" column="birthday"/> 
         <result property="userAddress" column="address"/> 
         <result property="userSex" column="sex"/> 
      </resultMap>

      Mybatis的连接池

    数据源是执行具体的数据库的相关信息,在配置文件中配置的,如mybatis.xml中的。在 Mybatis 中,数据源 dataSource 共有三类,分别是:

  • UNPOOLED : 不使用连接池的数据源。采用传统的 javax.sql.DataSource 规范中的连接池,Mybatis 中有针对规范的实现

  • POOLED : 使用连接池的数据源。采用池的思想

  • JNDI : 使用 JNDI 实现的数据源,采用服务器提供的 JNDI 技术实现,来获取 DataSource 对象,不同的服务器所能拿到的 DataSource 是不一样的。注意,如果不是 Web 或者 Maven 的war工程,是不能使用 JNDI 的。

Mybatis多表查询

多对一(一对多)

(1)编写联合查询sql语句。

SELECT U.*, a.id AS aid, a.uid, a.money from account a, user u WHERE a.uid = u.id;

(2)定义Bean,包含对相关实体类的引用(组合)。

(3)定义ResultMap。注意使用<association></association>对其中包含的实体类进行定义,该标签 用于一对一映射,其中的 property 属性表示要关联的属性,javaType 表示待关联的实体类的全限定类名。

多对多

(1)编写多对多查询语句

SELECT u.*, r.id as rid, r.role_name, r.role_desc FROM user u LEFT OUTER JOIN user_role ur ON u.id = ur.uid LEFT OUTER JOIN role r ON ur.rid = r.id;

(2)定义Bean,包含相关实体类的集合引用,如List<User>等。

(3)定义ResultMap,注意使用<collection></collection>对其中包含的实体类进行定义,该标签 用于一对一映射,其中的 property 属性表示要关联的属性,ofType 表示待关联的实体类的全限定类名。

Mybatis拦截器

(1)Mybatis拦截器只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。这是在Mybatis的Configuration中写死了的,如果要支持拦截其他接口就需要我们重写Mybatis的Configuration。Mybatis可以对这四个接口中所有的方法进行拦截。

(2)利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用StatementHandler对象的prepare方法,即调用invocation.proceed()

【myBatis】Mybatis中的拦截器_程序员面试经验分享-CSDN博客_mybatis拦截器

img

MyBatis学习——第四篇(拦截器和拦截器分页实现)_huyiju的博客-CSDN博客_分页拦截器

Mybatis的加载与缓存

加载

延迟加载

  • 延迟加载就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据,延迟加载也称懒加载

  • 一对多多对多的表关系中,通常情况下我们都是采用延迟加载。

  • 实现
    在mybatis.xml中配置开启延迟加载

    <!-- 开启延迟加载 -->
    <settings>
     <setting name="lazyLoadingEnabled" value="true"/>
     <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
  • 注意事项
    在编写 Mybatis 的配置文件时,文档结构一定不可以随便写,一定要按照官方文档所要求的顺序,比如说:<settings></settings> 标签不可以写在 <environments></environments>下方。具体文档结构见下图:

img

立即加载

  • 立即加载就是不管是否需要数据,只要一进行查询,就会把相关联的数据一并查询出来

  • 多对一一对一的表关系中,通常情况下我们都是采用立即加载。

缓存

一级缓存

一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flushclose,它就会存在。当调用 SqlSession 的**修改、添加、删除、commit()、close()、clearCache()**等方法时,就会清空一级缓存。

img

  • 第一次发起查询用户 id 为 1 的用户信息,Mybatis 会先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。

  • 得到用户信息,将用户信息存储到一级缓存中

  • 如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),那么 Mybatis 就会清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读

  • 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。

  • 注意事项

    • Mybatis 默认就是使用一次缓存的,不需要配置。
    • 一级缓存中存放的是对象。(一级缓存其实就是 Map 结构,直接存放对象)

二级缓存

二级缓存是 Mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 SQL 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的

img

  • 当 sqlSession1 去查询用户信息的时候,Mybatis 会将查询数据存储到二级缓存中。

  • 如果 sqlSession3 去执行相同 Mapper 映射下的 SQL 语句,并且执行 commit 提交,那么 Mybatis 将会清空该 Mapper 映射下的二级缓存区域的数据。

  • sqlSession2 去查询与 sqlSession1 相同的用户信息,Mybatis 首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。

  • Mybatis 的二级缓存配置

  • mybatis.xml

<settings> <!-- 开启缓存 --> <setting name="cacheEnabled" value="true"/></settings>
  • UserMapper.xml
<mapper namespace="cn.ykf.mapper.UserMapper">
   <!-- 使用缓存 -->
    <cache/>
</mapper>
  • 在需要使用二级缓存的操作上配置 (针对每次查询都需要最新数据的操作,要设置成 useCache=”false”,禁用二级缓存)
<select id="listAllUsers" resultMap="UserWithAccountsMap" useCache="true">   SELECT * FROM user
</select>
  • 注意事项
    • 当我们使用二级缓存的时候,所缓存的类一定要实现 java.io.Serializable 接口,这样才可以使用序列化的方式来保存对象。
    • 由于是序列化保存对象,所以二级缓存中存放的是数据,而不是整个对象。

Mybatis注解开发

常用注解表:

img

单表CURD

public interface UserMapper {
  /** * 查询所有用户
   \* * 
  @return
   */
   @Select("SELECT * FROM user")
   @Results(id = "UserMap",value = {
     @Result(id = true,property = "userId",column = "id"),
     @Result(property = "userName",column = "username"),
     @Result(property = "userBirthday",column = "birthday"),
     @Result(property = "userSex",column = "sex"),
     @Result(property = "userAddress",column = "address"), })
   List<User> listAllUsers();
   /** * 添加用户
   \* * 
  @param user
   *
   @return 成功返回1,失败返回0
   */
   @Insert("INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})")
   @ResultMap("UserMap")
   int saveUser(User user);

多对一(一对多)

public interface AccountMapper {

/**

  \* 查询所有账户,并查询所属用户,采用立即加载

  *

  \* @return

  */

  @Select("SELECT * FROM account")

  @Results(id = "AccountMap", value = {

      @Result(id = true, property = "id", column = "id"),

     @Result(property = "uid", column = "uid"),

      @Result(property = "user", column = "uid",

          one = @One(select = "cn.ykf.mapper.UserMapper.getUserById", fetchType = FetchType.EAGER))

  })

  List<Account> listAllAccounts();

}

public interface UserMapper {

  /**

  \* 根据id查询单个用户

  *

  \* @param userId

  \* @return

  */

  @Select("SELECT * FROM user WHERE id = #{id}")

  User getUserById(Integer userId);

}

多对多

public interface UserMapper {

  /**

  \* 查询所有用户,并且查询拥有账户,采用延迟加载

  *

  \* @return

  */

  @Select("SELECT * FROM user")

  @Results(id = "UserMap", value = {

      @Result(id = true, property = "userId", column = "id"),

      @Result(property = "userName", column = "username"),

      @Result(property = "userBirthday", column = "birthday"),

      @Result(property = "userSex", column = "sex"),

      @Result(property = "userAddress", column = "address"),

      @Result(property = "accounts", column = "id",

          many = @Many(select = "cn.ykf.mapper.AccountMapper.getAccountByUid", fetchType = FetchType.LAZY))

  })

  List<User> listAllUsers();

public interface AccountMapper {

  /**

  \* 根据用户id查询账户列表

  *

  \* @param uid

  \* @return

  */

  @Select("SELECT * FROM account WHERE uid = #{uid}")

  List<Account> listAccountsByUid(Integer uid);

}

二级缓存

<settings>
   <!-- 开启缓存 -->
   <setting name="cacheEnabled" value="true"/>
</settings>
@CacheNamespace(blocking = true)
public interface UserMapper {
 // .....
 }

Mybatis分页

主要有三种方法实现,最简单的就是利用原生的sql关键字limit来实现,还有一种就是利用interceptor来拼接sql,实现和limit一样的功能,再一个就是利用PageHelper来实现。

自定义返回对象Pager

public class Pager<T> {

  private int page;//分页起始页

  private int size;//每页记录数

  private List<T> rows;//返回的记录集合

  private long total;//总记录条数

}

limit关键字实现

  • UserMapper.java
public List<User> findByPager(Map<String, Object> params);
public long count();
  • UserMapper.xml
<select id="findByPager" resultType="com.xxx.mybatis.domain.User">
   select * from xx_user limit #{page},#{size}
</select>
<select id="count" resultType="long">
  select count(1) from xx_user
</select>
  • UserService.java
public Pager<User> findByPager(int page,int size){
   Map<String, Object> params = new HashMap<String, Object>();

   params.put("page", (page-1)*size);

   params.put("size", size);

   Pager<User> pager = new Pager<User>();

   List<User> list = userMapper.findByPager(params);

   pager.setRows(list);

   pager.setTotal(userDao.count());

   return pager;

 }

Interceptor plugin实现

定义一个类实现Interceptor接口,通过拦截器插件的方式实现分页。

  • MyPageInterceptor.java
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class,Integer.class})})

public class MyPageInterceptor implements Interceptor {

  private int page;

  private int size;

  @SuppressWarnings("unused")
  private String dbType;

  @SuppressWarnings("unchecked")
  @Override
  public Object intercept(Invocation invocation) throws Throwable {

  System.out.println("plugin is running...");

  StatementHandler statementHandler = (StatementHandler)invocation.getTarget();

  MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

  while(metaObject.hasGetter("h")){

  Object object = metaObject.getValue("h");

  metaObject = SystemMetaObject.forObject(object);

  }

  while(metaObject.hasGetter("target")){

  Object object = metaObject.getValue("target");

  metaObject = SystemMetaObject.forObject(object);

  }

  MappedStatement mappedStatement =
  (MappedStatement)metaObject.getValue("delegate.mappedStatement");
  String mapId = mappedStatement.getId();
  if(mapId.matches(".+ByPager$")){
    ParameterHandler parameterHandler =     (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
    Map<String, Object> params = (Map<String,Object>)parameterHandler.getParameterObject();
    page = (int)params.get("page");
    size = (int)params.get("size");
  String sql = (String) metaObject.getValue("delegate.boundSql.sql");
    sql += " limit "+(page-1)*size +","+size;
    metaObject.setValue("delegate.boundSql.sql", sql);
  }
  return invocation.proceed();
}
@Override
public Object plugin(Object target) {
  return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
  String limit = properties.getProperty("limit","10");
  this.page = Integer.parseInt(limit);
  this.dbType = properties.getProperty("dbType", "mysql");
  }
}
  • UserService.java
public Pager<User> findByPager(int page,int size){
  Map<String, Object> params = new HashMap<String, Object>();
  params.put("page", page);
  params.put("size", size);
  Pager<User> pager = new Pager<User>();
  List<User> list = userMapper.findByPager(params);
  pager.setRows(list);
  pager.setTotal(userMapper.count());
  return pager;
}
  • spring配置中,增加plugin设置

img

  • UserMapper.xml
<select id="findByPager" resultType="com.xxx.mybatis.domain.User">
 select * from xx_user 
</select>
<select id="count" resultType="long">
  select count(1) from xx_user
</select>

Pagehelper实现

  • maven依赖
<dependency>
   <groupId>com.github.pagehelper</groupId>
   <artifactId>pagehelper</artifactId>
   <version>4.2.1</version>
</dependency>
  • spring.xml
<bean id="pageInterceptor" class="com.github.pagehelper.PageHelper">
   <property name="properties">
    <props>
      <prop key="helperDialect">mysql</prop>
      <prop key="reasonable">true</prop>
      <prop key="supportMethodsArguments">true</prop>
      <prop key="params">count=countSql</prop>
    </props>
   </property>
 </bean>
 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="dataSource" />
   <property name="mapperLocations"     value="classpath:com/xxx/mybatis/dao/*Mapper.xml"/>
   <property name="plugins" ref="pageInterceptor"></property>
</bean>
  • UserService.java
 public Pager<User> findByPager(int page,int size){
   Pager<User> pager = new Pager<User>();
   Page<User> res = PageHelper.startPage(page,size);
   userMapper.findAll();
   pager.setRows(res.getResult());
   pager.setTotal(res.getTotal());
   return pager;
}

其实PageHelper方法也是第二种使用Interceptor拦截器方式的一种三方实现,它内部帮助我们实现了Interceptor的功能。所以我们不用自定义MyPageInterceptor这个类了。实际上也是在运行查询方法的时候,进行拦截,然后设置分页参数。所以PageHelper.startPage(page,size)这一句需要显示调用,然后再执行userMapper.findAll(),在查询所有用户信息的时候,会进行一个分页参数设置,让放回的结果只是分页的结果,而不是全部集合。

浅析pagehelper分页原理_王大锤的博客-CSDN博客_pagehelper分页原理

总结:PageHelper首先将前端传递的参数保存到page这个对象中,接着将page的副本存放入ThreadLoacl中,这样可以保证分页的时候,参数互不影响,接着利用了mybatis提供的拦截器,取得ThreadLocal的值,重新拼装分页SQL,完成分页。

常见问题汇总

关于mybatis的一些面试题

(1)#{}是sql占位符,在预编译时为“?”;${}是配置文件中的占位符,直接被替换成字符。

(2)mybatis将<select><insert>等标签,唯一解释为一个MappedStatement对象。

(3)Mybatis 仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybaytis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能。

开发常见问题总结

数据库和实体类映射

  • 驼峰命名

数据库表列:user_name

实体类属性:userName

在springboot的yml配置如下,一定不要放在spring标签下!!!

mybatis:
configuration:
# 开启驼峰uName自动映射到u_name
map-underscore-to-camel-case: true

或者在mybatis配置文件中设置:

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

  • 使用别名。参考3.1节

Parameter array not found. Available parameters are [collection, list]问题

当我们要查询一些的信息时,可能会采用list集合或者数组作为参数传入方法中。

<select id="findSomeUsers" resultType="user3" parameterType="list">
   select * from user where id in
  <foreach collection="noList" index="index" item="no" open="(" separator="," close=")">
    #{no}
  </foreach>
 </select>

这时报错是因为,传递一个 List 实例或者数组作为参数对象传给 MyBatis,MyBatis 会自动将它包装在一个 Map 中,用名称在作为键。List 实例将会以“list” 作为键,而数组实例将会以“array”作为键。解决这个异常的两种方式是:
1.在方法参数前面加上你遍历的集合的名称,比如你在foreach的collection中写的是noList,那么你就在传入的list参数前面加上一个注解@Param(“noList”)。
2.将foreach的collection中的值改成list即可。

  • 在实际开发中,上述报错,下面代码为一项目中使用的正确代码:
@Select({ "<script>", "select * from `product_category` where category_type in ",

"<foreach collection='categoryTpyeList' item='item' index='index' open='(' separator=',' close=')'>",

"#{item}", "</foreach>", "</script>" })

public List<ProductCategoryEntity> findByCategoryTpye(@Param("categoryTpyeList") List<Integer> categoryTpyeList);

collection: 指定要遍历的集合(三种情况 list,array,map) !!!!在这种使用注解sql的情况下,这里请填写mapper方法中集合的名称

item:将当前遍历出的元素赋值给指定的变量 (相当于for循环中的i)

separator:每个元素之间的分隔符

index:索引。遍历list的时候是index就是索引,item就是当前值

#{变量名}就能取出变量的值也就是当前遍历出的元素


文章作者: 小小千千
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 小小千千 !
评论
  目录