title: Day63-使用MyBatis
date: 2021-03-31 16:09:06
author: Liu_zimo
-
回顾Mybatis
-
SqlSessionFactoryBuilder接收SqIMapConfig.xml文件流,构建出SqlSessionFactory对象
-
SqlSessionFactory读取SqIMapConfig.xml中连接数据库和mapper映射信息。用来生产出真正操作数据库的SqlSession对象
-
SqlSession对象有两大作用
①生成接口代理对象
②定义通用增删改查方法注:无论哪个分支,除了连接数据库信息,还需要得到sql语句
-
作用
- 作用①:在SqlSessionlmpl对象的getMapper方法中分两步来实现
- 第一:先用SqlSessionFactory读取的数据库连接信息创建Connection对象
- 第二:通过jdk代理模式创建出代理对象作为getMapper方法返回值,这里主要工作是在创建代理对象时第三个参数处理类里面得到sql语句。执行对应CRUD操作
- 作用②:在SqlSessionlmpl对象中提供selectList()方法,【当然实际mybatis框架中还有selectOne,insert等方法】这些方法内也分两步。
- 第一:用SqlSessionFactory读取的数据库连接信息创建出jdbc的Connection对象
- 第二:直接得到sql语句,使用jdbc的Connection对象进行对应的CRUD操作。
- 作用①:在SqlSessionlmpl对象的getMapper方法中分两步来实现
-
封装结果集:无论使用分支一生成代理对象,还是直接使用分支二提供的通用CRUD方法,我们都要对返回的数据库结果集进行封装,变成java对象返回给调用者。所以我们还必须要知道调用者所需要的返回类型
- 总结:通过以上流程我们不难看出,无论是让mybatis帮我们创建代理对象还是直接使用mybatis提供
-
Mybatis基于代理Dao的CRUD
package com.zimo.dao;
import com.zimo.domain.User;
import com.zimo.mybatis.annotations.Select;
import java.util.List;
/**
* 用户持久层接口
* @author Liu_zimo
* @version v0.1 by 2021/3/30 18:09
*/
public interface IUserDao {
// 查询所有
List<User> findAll();
// 保存
void saveUser(User user);
// 更新
void updateUser(User user);
// 删除
void deleteUser(int id);
// 模糊查询
void findByName(String name);
}
<?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.zimo.dao.IUserDao">
<!-- 配置查询所有-->
<select id="findAll" resultType="com.zimo.domain.User">
select * from user;
</select>
<insert id="saveUser" parameterType="com.zimo.domain.User">
<!-- 配置插入后,获取插入数据的id -->
<selectKey keyProperty="id"keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user (username, address, sex, birthday) values (#{username},#{address},#{sex},#{birthday});
</insert>
<update id="updateUser" parameterType="com.zimo.domain.User">
update user set username = #{username}, address = #{address},sex = #{sex}, birthday = #{birthday} where id = #{id};
</update>
<delete id="deleteUser" parameterType="int">
delete from user where id = #{uid};
</delete>
<select id="findByName" parameterType="string" resultType="com.zimo.domain.User">
select * from user where username like #{name}; <!-- 需要传%% -->
<!-- select * from user where username like '%${value}%' --> <!-- 不用传%% 注:value是固定的 -->
</select>
</mapper>
- PrepatedStatement的参数占位符
Preparing:select * from user where username like ?
- Statement对象的字符串拼接SQL
Preparing:select * from user where username like %王%
Mybatis参数深入
parameterType(输入类型)
-
传递简单类型:int、String…
-
传递pojo对象:Mybatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称。
- 开发中通过pojo传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。Pojo类中包含pojo
- 需求:根据用户名查询用户信息,查询条件放到QueryVo的user属性中。
<select id="findByName" parameterType="string" resultType="com.zimo.domain.User"> select * from user where username like #{name}; <!-- 需要传%% --> </select> <select id="findByVo" parameterType="com.zimo.domain.QueryVo" resultType="com.zimo.domain.User"> select * from user where username like #{user.username}; <!-- 需要传%% --> </select>
package com.zimo.domain; /** * @author Liu_zimo * @version v0.1 by 2021/3/31 18:13 */ public class QueryVo { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } } @Test public void testQueryVo(){ QueryVo queryVo = new QueryVo(); User user = new User(); user.setUsername("%王%"); queryVo.setUser(user); List<User> byVo = userDao.findByVo(queryVo); for (User user1 : byVo) { System.out.println(user1); } }
- OGNL表达式:Object Graph Navigation Language(对象,图,导航,语言)
- 它是通过对象的取值方法来获取数据。在写法上把get给省略了
- 比如:我们获取用户的名称
- 类中的写法:
user.getUsername();
- OGNL表达式写法:
user.username
- 类中的写法:
- mybatis中为什么能直接写username,而不用user.呢:
- 因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名
resultType(输出类型)
- 传递简单类型:int、String…
- 传递pojo对象或者pojo列表
- resultType可以指定 pojo将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。
resultMap
如果sql查询字段名和 pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系,resultMap实质上还需要将查询结果映射到pojo对象中。
resultMap可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括pojo和 list实现一对一查询和一对多查询。
-
当程序类封装的名称和数据表字段名对不上时有两种解决方式
-
别名
select id as userId, username as userName, address as userAddress,... from user;
执行效率高,在sql层面解决了问题 -
配置方式:开发效率高
<resultMap id="userMap" type="com.zimo.domain.User"> <!-- 主键字段的对应 --> <id property="userId" column="id"></id> <!-- 非主键字段的对应 --> <result property="userName" column="username"></result> <result property="userAddress" column="address"></result> <result property="userSex" column="sex"></result> <result property="userBirthday" column="birthday"></result> </resultMap> <select id="findAll" resultMap="userMap"> select * from user; </select>
-
不使用代理自实现Dao分析
- findAll()方法:
select * from user
1. List<User> list = session.selectList("com.zimo.dao.IUserDao.findAll"); // 执行查询所有方法
// DefaultSqlSession
2. this.selectList(statement, (Object)null); // 调用默认实现
3. this.selectList(statement, parameter, RowBounds.DEFAULT);
4. var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
// Executor - CachingExecutor
5. public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
6. return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// Executor - BaseExecutor
7. list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
8. list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
// BaseExecutor - SimpleExecutor
9. return list = handler.query(stmt, resultHandler);
// RoutingStatementHandler
10. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return this.delegate.query(statement, resultHandler);
}
// StatementHandler - PreparedStatementHandler
11. public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}
// BaseStatementHandler
12. protected final ResultSetHandler resultSetHandler;
// ResultSetHandler - interface
// DefaultResultSetHandler - List<Object> handleResultSets(Statement stmt) 封装结果集
13. ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
14. ......
15. this.collapseSingleResultList(multipleResults);
- PreparedStatement对象他的执行方法
- execute:它能执行CRUD中的任意一种语句。它的返回值是一个boolean类型,表示是否有结果集。有结果集是true,没有结果集是fasle
- executeUpdate:它只能执行CUD语句,查询语句无法执行。他的返回值是影响数据库记录的行数
- executeQuery:它只能执行SELECT语句,无法执行增删改。执行结果封装的结果集ResultSet对象
- saveUser()方法:
insert into user (username, address, sex, birthday) values (#{username},#{address},#{sex},#{birthday});
- deleteUser()、updateUser()方法类似,最终都会调用到
public int update(String statement, Object parameter)
1. session.insert("com.zimo.dao.IUserDao.saveUser", user); // 执行保存方法
// DefaultSqlSession - SqlSession
2. public int insert(String statement, Object parameter) { return this.update(statement, parameter); }
3. public int update(String statement, Object parameter) {
this.executor.update(ms, this.wrapCollection(parameter));
}
// Executor - CachingExecutor
4. public int update(MappedStatement ms, Object parameterObject) throws SQLException {
this.flushCacheIfRequired(ms);
return this.delegate.update(ms, parameterObject);
}
// Executor - BaseExecutor
5. return this.doUpdate(ms, parameter);
6. protected abstract int doUpdate(MappedStatement var1, Object var2) throws SQLException;
// BaseExecutor - SimpleExecutor - public int doUpdate(MappedStatement ms, Object parameter)
7. return handler.update(stmt);
// RoutingStatementHandler
8. public int update(Statement statement) throws SQLException {
return this.delegate.update(statement);
}
// StatementHandler - PreparedStatementHandler
9. public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = this.boundSql.getParameterObject();
KeyGenerator keyGenerator = this.mappedStatement.getKeyGenerator();
keyGenerator.processAfter(this.executor, this.mappedStatement, ps, parameterObject);
return rows;
}
分析代理Dao执行过程
in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
sqlSession = factory.openSession();
userDao = sqlSession.getMapper(IUserDao.class);
// SqlSession - DefaultSqlSession
1. public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
// Configuration
2. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry
3. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
return mapperProxyFactory.newInstance(sqlSession);
}
// MapperProxyFactory
4. public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) { // 动态代理
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
// mapperProxy代理过程执行方法类
5. public class MapperProxy<T> implements InvocationHandler, Serializable {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
// MapperMethod
6. public Object execute(SqlSession sqlSession, Object[] args) {
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
....
break;
case DELETE:
....
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
}
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}
if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
properties配置使用
- 多此一举的改造
<?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">
<!-- mybatis主配置文件 -->
<configuration>
<!-- 配置properties -->
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>
- 支持外部配置
# resources jdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=1234
<!-- mybatis主配置文件 -->
<configuration>
<!--
配置properties可以在标签内部配置连接数据库的信息。也可以通过属性引用外部配置文件信息
resource属性:常用属性
用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
url:
是要求按昭Url的写法来写地址
URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置。
它的写法:
http://localhost:8080/mybatisserver/demo1Servlet
协议 主机 端口 URI
-->
<properties resource="jdbcConfig.properties"></properties>
<!--两者是一样的-->
<properties url="file:///D:/java/com/zimo/src/main/resources/jdbcConfig.properties"></properties>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql环境 -->
<environment id="mysql">
<!-- 配置事务的类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置连接数据库的4个基本信息 -->
<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>
<mappers>
<mapper resource="com/zimo/dao/IUserDao.xml" />
</mappers>
</configuration>
<typeAliases>
<!--typeAlias用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名,当指定了别名就再区分大小写-->
<typeAlias type="com.zimo.domain.User" alias="user"></typeAlias>
</typeAliases>
<select id="findAll" resultType="USER">
select * from user;
</select>
<update id="updateUser" parameterType="UsEr">
update user set username = #{username}, address = #{address},sex = #{sex}, birthday = #{birthday} where id = #{id};
</update>
<delete id="deleteUser" parameterType="user">
delete from user where id = #{uid};
</delete>
------------------------------------------------------------------------------------------
<typeAliases>
<!--用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
<package name="com.zimo.domain"></package>
</typeAliases>
<mappers>
<!-- <mapper resource="com/zimo/dao/IUserDao.xml" /> -->
<!-- package标签是用于指定dao接口所在的包,当指定了之后就不需要在写mapperl以及resource或者class了-->
<package name="com.itheima.dao"></package>
</mappers>
MyBatis核心配置文件概述
- configuration配置
- properties属性
- setting设置
- typeAliases类型别名
- typeHandlers类型处理器
- objectFactory对象工厂
- plugins插件
- environments环境
- environment环境变量
- transactionManager事务管理器
- dataSource数据源
- environment环境变量
- databaseldProvider数据库产商标识
- mappers映射器
environments标签
数据库环境的配置,支持多环境配置
<environments default="mysql"> # 指定默认的环境名称
<environment id="mysql"> # 指定当前的环境名称
<transactionManager type="JDBC"></transactionManager> # 指定事务管理类型是jdbc
<dataSource type="POOLED"> # 指定当前数据源类型是连接池
<!-- 配置连接数据库的4个基本信息 -->
<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>
其中,事务管理器(transactionManager)类型有两种:
- JDBC:这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域
- MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如JEE应用服务器的上下文)。默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将closeConnection属性设置为false来阻止它默认的关闭行为。
其中,数据源(dataSource)类型有三种:
- UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接
- POOLED:这种数据源的实现利用“池”的概念将JDBC连接对象组织起来
- JNDI:这个数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用。
mapper标签
该标签的作用是加载映射的,加载方式有如下几种:
- 使用相对于类路径的资源引用,例如:
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
- 使用完全限定资源定位符(URL),例如
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
- 使用映射器接口实现类的完全限定类名,例如:
<mapper class="org.mybatis.builder.AuthorMapper"/>
- 将包内的映射器接口实现全部主册为映射器,例如:
<package name="org.mybatis.builder"/>
properties标签
实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的properties文件
<!--通过properties标签加载外部properties文件-->
<properties resource="jdbc.properties"></properties>
typeAliases标签
类型别名是为Java类型设置一个短的名字。原来的类型名称配置如下
<select id="findAll" resultType="com.itheima.domain.User">
select * from User
</select>
配置typeAliases,为com.itheima.domain.User定义别名为user
<typeAliases>
<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>
</typeAliases>
------------------------------------------------------------------------
<select id="findAll" resultType="user">
select* from User
</select>
上面我们是自定义的别名,mybatis框架已经为我们设置好的一些常用的类型的别名
别名 | 数据类型 |
---|---|
string | String |
long | Long |
int | Integer |
double | Double |
boolean | Boolean |
… | … |
typeHandlers标签
无论是MyBatis在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成Java类型。下表描述了一些默认的类型处理器(截取部分)
类型处理器 | Java类型 | JDBC 类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean,boolean | 数据库兼容的 BOOLEAN |
ByteTypeHandler | java.lang.Byte,byte | 数据库兼容的 NUMERIC 或 BYTE |
shortTypeHandler | java.lang.short,short | 数据库兼容的 NUMERIC 或 SHORT INTEGER |
IntegerTypeHandler | java.lang.Integer,int | 数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long,long | 数据库兼容的 NUMERIC 或 LONG INTEGER |
你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。具体做法为:实现org.apache.ibatis.type.TypeHandler接口,或继承一个很便利的类org.apache.ibatis.type.BaseTypeHandler,然后可以选择性地将它映射到一个JDBC类型
例如:一个Java中的Date数据类型,我想将之存到数据库的时候存成一个1970年至今的毫秒数,取出来时转换成java的Date,即java的Date与数据库的varchar毫秒值之间转换
- 开发步骤:
- 定义转换类继承类BaseTypeHandler<T>
- 覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult为查询时mysql的字符串类型转换成java的Type类型的方法
- 在MyBatis核心配置文件中进行注册
- 测试转换是否正确
plugins标签
MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据
- 开发步骤:
- 导入通用PageHelper的坐标
- 在mybatis核心配置文件中配置PageHelper插件
- 测试分页数据获取
MyBatis相关API
SqlSession工厂构建器SqlSessionFactoryBuilder
常用API:SqlSessionFactory build(InputStream inputStream),通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象
public void function(){
string resource = "org/mybatis/builder/mybatis-config.xml";
Inputstream inputstream = Resources.getResourceAsstream(resource);
SqlsessionFactoryBuilder builder = new SqlsessionFactoryBuilder();
SqlsessionFactory factory = builder.build(inputstream);
}
其中,Resources工具类,这个类在org.apache.ibatis.io包中。Resources类帮助你从类路径下、文件系统或一个web URL中加载资源文件。
SqlSession工厂对象SqlSessionFactory
SqlSessionFactory有多个个方法创建SqlSession实例。常用的有如下两个:
方法 | 解释 |
---|---|
openSession() | 会默认开启一个事务,但事务不会自动提交,也就意味着需要手动提交该事务,更新操作数据才会持久化到数据库中 |
openSession(boolean autoCommit) | 参数为是否自动提交,如果设置为true,那么不需要手动提交事务 |
SqlSession会话对象
Sqlsession实例在MyBatis中是非常强大的一个类。在这里你会看到所有执行语句、提交或回滚事务和获取映射器实例的方法。执行语句的方法主要有:
<T> T selectone (string statement,object parameter)
<E> List<E> selectList (string statement,object parameter)
int insert(string stafement,object parameter)
int update (string statement,object parameter)
int delete (string statement,object parameter)
操作事务的方法主要有:
void commit()
void rollback()