目录
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程序就能访问不同的数据库。
JDBC实现
不同的数据库提供不同的JDBC实现,如图1-2所示,JDBC的实现包括三部分。
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();
}
}
}
- 构建者模式:创建SqlSessionFactory对象
- 工厂模式: 创建SqlSession对象
- 代理模式: 创建Dao接口实体类。getMapper(类名.class)方法,使用JDK动态代理创建一个代理对象,在对象的InvocationHandler接口实现类中调用findAll()方法。最后使用反射技术封装实体类返回代理对象。
源码分析
在通过代理模式生成代理对象后,调用方法如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拦截器
MyBatis学习——第四篇(拦截器和拦截器分页实现)_huyiju的博客-CSDN博客_分页拦截器
Mybatis的加载与缓存
加载
延迟加载
延迟加载就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据,延迟加载也称懒加载。
在一对多或多对多的表关系中,通常情况下我们都是采用延迟加载。
实现
在mybatis.xml中配置开启延迟加载<!-- 开启延迟加载 --> <settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>
注意事项
在编写 Mybatis 的配置文件时,文档结构一定不可以随便写,一定要按照官方文档所要求的顺序,比如说:<settings></settings>
标签不可以写在<environments></environments>
下方。具体文档结构见下图:
立即加载
立即加载就是不管是否需要数据,只要一进行查询,就会把相关联的数据一并查询出来。
在多对一或一对一的表关系中,通常情况下我们都是采用立即加载。
缓存
一级缓存
一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就会存在。当调用 SqlSession 的**修改、添加、删除、commit()、close()、clearCache()**等方法时,就会清空一级缓存。
第一次发起查询用户 id 为 1 的用户信息,Mybatis 会先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果 sqlSession 去执行 commit 操作(执行插入、更新、删除),那么 Mybatis 就会清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
注意事项
- Mybatis 默认就是使用一次缓存的,不需要配置。
- 一级缓存中存放的是对象。(一级缓存其实就是 Map 结构,直接存放对象)
二级缓存
二级缓存是 Mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 SQL 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
当 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注解开发
常用注解表:
单表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设置
- 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就是当前值
#{变量名}就能取出变量的值也就是当前遍历出的元素