MyBatis
MyBatis
原始jdbc操作的分析
原始jdbc开发存在的问题如下:
- 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能
- sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能性较大,sql变动需要改变java代码
- 查询操作时,需要手动将结果集中的数据手动封装到实体中,插入操作时,需要手动将实体的数据设置到sql语句的占位符位置
应对上述问题给出的解决方案:
- 使用数据库连接池初始化连接资源
- 将sql语句抽取到xml配置文件中
- 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射
什么是MyBatis
mybatis是一个优秀的基于Java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程
mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句
最后mybatis框架执行sql并将结果映射为java对象并返回,采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作
开发步骤
Mybatis官网地址:https://www.mybatis.org/mybatis-3/
Mybatis开发步骤:
-
添加Mybatis的坐标
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency>
-
创建user数据表
-
编写User实体类
-
编写映射文件
UserMapper.xml
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="userMapper"> <!-- 删除操作 --> <delete id="delete" parameterType="java.lang.Integer"> DELETE FROM user WHERE id = #{id} </delete> <!-- 修改操作 --> <update id="update" parameterType="com.domain.User"> UPDATE user SET username=#{username},password=#{password} WHERE id=#{id} </update> <!-- 插入操作 --> <insert id="save" parameterType="com.domain.User"> INSERT INTO user VALUES(#{id},#{username},#{password}) </insert> <!-- 查询操作 --> <select id="findAll" resultType="com.domain.User"> SELECT * FROM user </select> </mapper>
-
编写核心文件
SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <configuration> <!-- 数据源环境 --> <environments default="development"> <!-- default是默认情况下环境 --> <environment id="development"> <!-- 事务管理器 --> <transactionManager type="JDBC"></transactionManager> <!-- 数据源类型 --> <dataSource type="POOLED"> <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="root"/> </dataSource> </environment> </environments> <!-- 加载映射文件 --> <mappers> <mapper resource="com/mapper/UserMapper.xml"> </mapper> </mappers> </configuration>
-
编写测试类
// 加载核心配置文件 InputStream res = Resources.getResourceAsStream("SqlMapConfig.xml"); // 获得sqlSession工厂对象 SqlSessionFactory sqlF = new SqlSessionFactoryBuilder().build(res); // 获得sqlSession会话对象 SqlSession sqlS = sqlSessionFactory.openSession(); // 执行sql语句 参数:namespace + id sqlS.delete("userMapper.delete",7) sqlS.update("userMapper.update",user); sqlS.insert("userMapper.save",user); List<User> userList = sqlS.selectList("userMapper.findAll"); // mybatis执行更新操作 提交事务 sqlSession.commit(); // 打印结果 System.out.println(userList); // 释放资源 sqlS.close();
插入数据操作
插入操作注意问题:
- 插入语句使用
insert
标签 - 在映射文件中使用
parameterType
属性指定要插入的数据类型 - sql语句中使用
#{实体属性名}
方式引用实体中的属性值 - 插入操作使用的是API是
sqlSession.insert("命名空间.id",实体对象);
- 插入操作涉及数据库数据变化,所以要使用
sqlSession
对象显式的提交事务,即sqlSession.commit()
修改数据操作
修改操作注意问题:
- 修改语句使用update标签
- 修改操作使用的API是
sqlSession.update("命名空间.id",实体对象);
删除数据操作
删除操作注意问题:
- 删除语句使用delete标签
- sql语句中使用
#{任意字符串}
方式引用传递的单个参数 - 删除操作使用的API是
sqlSession.delete("命名空间.id",Object);
核心配置文件概述
核心配置文件层级关系
- configuration 配置
- properties 属性
- settings 设置
- typeAliases 类型别名
- typeHandlers 类型处理器
- objectFactory 对象工厂
- plugins 插件
- environments 环境
- environment 环境变量
- transactionManager 事务管理器
- dataSource 数据源
- environment 环境变量
- databaseIdProvider 数据库厂商标识
- mappers 映射器
常用配置解析
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 resource="jdbc.properties"></properties>
<!-- 数据源环境 -->
<environments default="development"> <!-- default是默认情况下环境 -->
<environment id="development">
<!-- 事务管理器 -->
<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>
typeAliases标签
类别名称是为Java类型设置一个短的名字,原来的类型名称配置如下
<select id="findAll" resultType="com.domain.User">
select * from User
</select>
配置typeAliases
,为com.domain.User
定义别名为user
<typeAliases>
<typeAlias type="com.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 |
相应API
SqlSession工厂构建器SqlSessionFactoryBuilder
常用API:SqlSessionFactory build(InputStream inputStream)
通过加载mybatis的核心文件的输出流的形式构建一个SqlSessionFactory
对象
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 statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
操作事务的方法主要有:
void commit()
void rollback()
Dao层实现
传统开发方式
编写UserDao接口
public interface UserDao {
List<User> findAll() throws IOException;
}
代理开发方式
采用Mybatis的代理开发方式实现DAO层的开发,这种方式是我们后面进入企业的主流
Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边DAO接口实现类方法
Mapper接口开发需要遵循以下规范:
Mapper.xml
文件中的namespace
与Mapper接口的全限定名相同- Mapper接口方法名和
Mapper.xml
中定义的每个statement
的id相同 - Mapper接口方法的输入参数类型和
mapper.xml
中定义的每个sql的parameterType
的类型相同 - Mapper接口方法的输出参数类型和
mapper.xml
中定义的每个sql的resultType
的类型相同
<mapper namespace="com.mapper.UserDao">
<select id="findById" parameterType="int" resultType="user">
</select>
</mapper>
public interface UserDao {
User findById(int id);
}
测试代理方式
@Test
public void testProxyDao() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获得Mybatis框架生成的UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.findById(1);
System.out.println(user);
sqlSession.close();
}
映射文件深入
动态sql语句
Mybatis的映射文件中,前面我们的SQL都是比较简单的,有些时候业务逻辑复杂时,我们的SQL语句是动态变化的,此时在前面的学习中我们的SQL就不能满足要求了
<if>
我们根据实体类的不同取值,使用不同的SQL语句来进行查询,比如在id如果不为空时可以根据id查询,如果username
不同时还要加入用户名作为条件,这种情况下在我们的多条件组合查询中经常会碰到
<select id="findByCondition" parameterType="user" resultType="user">
select * from User
<where>
<if test="id!=0">
and id = #{id}
</if>
<if test="username!=null">
and username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
</where>
</select>
<foreach>
循环执行sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)
<select id="findByIds" parameterType="list" resultType="user">
select * from User
<where>
<foreach collection="array" open="id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
SQL片段抽取
sql中可将重复的sql提取出来,使用时用include
引用即可,最终达到sql重用的目的
<!-- 抽取sql片段简化编写 -->
<sql id="selectUser">select * from User</sql>
<select id="findById" parameterType="int" resultType="user">
<include refid="selectUser"></include> where id = #{id}
</select>
知识小结
Mybatis映射文件配置
<select>
:查询
<insert>
:插入
<update>
:修改
<delete>
:删除
<where>
:where条件
<if>
:if判断
<foreach>
:循环
<sql>
:sql片段抽取
核心配置文件深入
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核心配置文件中进行注册
<!-- 自定义别名 --> <typeAliases> <typeAlias type="com.domain.User" alias="user"></typeAlias> </typeAliases>
-
测试转换是否正确
plugins
标签
Mybatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper
是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据
开发步骤:
-
导入通用
PageHelper
的坐标<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>0.9.1</version> </dependency>
-
在mybatis核心配置文件中配置
PageHelper
插件<plugins> <plugin interceptor="com.github.pagehelper.PageHelper"> <!-- 方言的指定,比如MySQL --> <property name="dialect" value="mysql"></property> </plugin> </plugins>
-
测试分页数据获取
// 设置分页相关参数 当前页+每页显示的条数 PageHelper.startPage(1,3); // 获得与分页相关参数 PageInfo<User> pageInfo = new PageInfo<User>(userList);
知识小结
Mybatis核心配置文件常用标签:
properties
标签:该标签可以加载外部的properties
文件typeAliases
标签:设置类型别名environments
标签:数据源环境配置标签typeHandlers
标签:配置自定义类型处理器plugins
标签:配置Mybatis的插件
多表操作
一对一的配置实现
<resultMap id="orderMap" type="order">
<!-- 手动指定字段与实体属性的映射关系
column: 数据表的字段名称
property: 实体的属性名称
-->
<id column="oid" property="id"></id>
<result column="ordertime" property="ordertime"></result>
<result column="total" property="total"></result>
<!--
property: 当前实体(order)中的属性名称(private User user)
javaType: 当前实体(order)中的属性的类型(User)
-->
<association property="user" javaType="user">
<id column="uid" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
<select id="findAll" resultMap="orderMap">
SELECT ...
</select>
一对多的配置实现
<resultMap id="userMap" type="user">
<id column="uid" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<!-- 配置集合信息
property: 集合名称
ofType: 当前集合中的数据类型
-->
<collection property="orderList" ofType="order">
<!-- 封装order的数据 -->
<id column="oid" property="id"></id>
<result column="ordertime" property="ordertime"></result>
<result column="total" property="total"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT ...
</select>
多对多的配置实现
<resultMap id="userRoleMap" type="user">
<!-- user的信息 -->
<id column="userId" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<!-- user内部的roleList信息 -->
<collection property="roleList" ofType="role">
<id column="roleId" property="id"></id>
<result column="roleName" property="roleName"></result>
<result column="roleDesc" property="roleDesc"></result>
</collection>
</resultMap>
<select id="findUserAndRoleAll" resultMap="userRoleMap">
SELECT ...
</select>
知识小结
Mybatis多表配置方式
一对一配置:使用<resultMap>
做配置
一对多配置:使用<resultMap> + <collection>
做配置
多对多配置:使用<resultMap> + <collection>
做配置
注解开发
MyBatis的常用注解
Mybatis也可以使用注解开发方式,这样可以减少编写Mapper映射文件了
@Insert
:实现新增
@Update
:实现更新
@Delete
:实现删除
@Select
:实现查询
@Result
:实现结果集封装
@Results
:可以与@Result
一起使用,封装多个结果集
@One
:实现一对一结果集封装
@Many
:实现一对多结果集封装
@Insert("INSERT INTO user VALUES(#{id},#{username},#{password},#{birthday})")
public void save(User user);
@Update("UPDATE user SET username=#{username},password=#{password} WHERE id=#{id}")
public void update(User user);
@Delete("DELETE FROM user WHERE id=#{id}")
public void delete(int id);
@Select("SELECT * FROM user WHERE id=#{id}")
public User findById(int id);
MyBatis的注解实现复杂映射开发
实现复杂关系映射之前我们可以在映射文件中通过配置<resultMap>
来实现,使用注解开发后,我们可以使用@Results
注解,@Result
注解,@Many
注解组合完成复杂关系的配置
注解 | 说明 |
---|---|
@Results | 代替的是标签<resultMap> ,该注解中可以使用单个@Result注解,也可以使用@Result集合,使用格式:@Results({@Result(),@Result()})或@Results(@Result()) |
@Result | 代替了<id> 标签和<result> 标签,@Result中属性介绍:column:数据库的列名,property:需要装配的属性名,one: 需要使用的@One注解(@Result(one=@One)()),many:需要使用的@Many注解(@Result(many=@many)()) |
@One(一对一) | 代替了<assocation> 标签,是多表查询的关键,在注解中用来指定子查询返回单一对象,@One注解属性介绍:select:指定用来多表查询的sqlmapper,使用格式:@Result(column="",property="",one=@One(select="")) |
@Many(多对一) | 代替了<collection> 标签,是多表查询的关键,在注解中用来指定子查询返回对象集合,使用格式:@Result(property="",column="",many=@Many(select="")) |
一对一
@Select("SELECT *, o.id oid FROM orders o, user u WHERE o, uid = u.id")
@Results({
@Result(column = "oid", property = "id"),
@Result(column = "ordertime", property = "ordertime"),
@Result(column = "total", property = "total"),
@Result(column = "uid", property = "user.id"),
@Result(column = "username", property = "user.username"),
@Result(column = "password", property = "user.password")
})
public List<Order> findAll();
一对多
@Select("SELECT * FROM orders")
@Results({
@Result(column = "id", property = "id"),
@Result(column = "ordertime", property = "ordertime"),
@Result(column = "total", property = "total"),
@Result(
property = "user", // 要封装的属性名称
column = "uid", // 根据那个字段去查询user表的数据
javaType = User.class, // 要封装的实体类型
// select属性 代表查询哪个接口的方法获得数据
one = @One(select = "com.mapper.UserMapper.findById")
)
})
public List<Order> findAll();
多对多
@Select("SELECT * FROM user")
@Results({
@Result(id=true, column="id", property="id"),
@Result(column="username", property="username"),
@Result(column="password", property="password"),
@Result(
property = "orderList",
column = "id",
javaType = List.class,
many = @Many(select = "com.mapper.OrderMapper.findByUid")
)
})
public List<User> findUserAndOrderAll();