Java Web 后端技术(四)–MyBatis
MyBatis简介
原始数据库连接需要通过JDBC与数据库建立连接,并且需要注册驱动,配置一系列参数(Driver,URL,user,password)的前提下才能连接数据库进行操作。而每次操作数据库时都会对进行一系列的创建连接以及释放资源的操作,从而导致系统资源浪费的情况发生以至于降低系统性能。而对数据库操作需要编写sql语句,而在代码中硬编码,可能导致的结果是代码不容易维护,当在实际的应用中sql语句发生变化会导致一系列的Java代码发生变化,最后就会导致巨大的工作量。针对这一情况,所以产生了MyBatis。
MyBatis 本是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了google code,随着开发团队转投到Google Code旗下,iBatis正式改名为MyBatis ,代码于2013年11月迁移到Github。
MyBatis是一个优秀的基于ORM的半自动轻量级持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码.。
ORM(Object Relational Mapping) :对象关系映射
- O(对象模型):实体对象
主要介绍MyBatis这几个组成部分。
1.MyBatis基本应用
MyBatis开发流程:
- 创建maven工程,导入依赖(MySQL驱动、mybatis)
- 编写实体类
- 配置实体类对应的xml映射文件
- 配置核心配置文件:SqlMapConfig.xml
1.Maven坐标
<!--mybatis坐标-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!--mysql驱动坐标-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
<scope>runtime</scope>
</dependency>
2.User实体类
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// getter/setter/toString 略
}
3.UserMapper.xml映射文件
<mapper namespace="UserMapper">
<!--查询所有-->
<select id="findAll" resultType="com.lagou.domain.User">
select * from user
</select>
</mapper>
4.SqlMapConfig.xml核心配置文件
<configuration>
<!--环境配置-->
<environments default="mysql">
<!--使用MySQL环境-->
<environment id="mysql">
<!--使用JDBC类型事务管理器-->
<transactionManager type="JDBC"></transactionManager>
<!--使用连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///mybatis_db></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</dataSource>
</environment>
</environments>
<!--加载映射配置-->
<mappers>
<mapper resource="com/lagou/mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
数据库连接测试:
- 通过Resources加载核心配置文件SqlMapConfig.xm
- 获取SqlSessionFactory工厂对象
- 通过工厂类的openSession方法获取SQLSession会话对象
- 执行sql语句进行遍历查询打印
- 在完成查询打印操作后释放当前资源
// 加载核心配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
// 获取SqlSessionFactory工厂对象`在这里插入代码片`
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 获取SqlSession会话对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行sql
List<User> list = sqlSession.selectList("UserMapper.findAll");
for (User user : list) {
System.out.println(user);
}
// 释放资源
sqlSession.close();
UserMapper.xml映射文件配置:
<mapper namespace>
:根标签目录,namespace为命名空间,于sql语句中的id一起组成查询标签<select/insert/update/delete id ="" resultType="" parameterType=""><<select/insert/update/delete>
:标题头为对应的增删改查sql操作,id
为要执行的sql语句,resultType
为结果集所对应的答应在前台的格式,parameterType
为传入参数的数据格式。在中间编写对应的sql语句。- 在执行增删改的时候会传输数据,Sql语句中使用
#{实体属性名}
方式引用实体中的属性值 - MySQL数据库默认配置自动提交事务,然而在MyBatis中执行插入操作及涉及到数据库内数据变化的情况需要使用SQLSession对象显示的提交事务:
sqlSession.commit();
SqlMapConfig配置文件层级关系:
注 :在配置MyBatis核心配置文件时需要注意层级关系,必须要按照configuration的层级顺序配置。
1、environments标签
标签 | 配置 |
---|---|
transactionManager | JDBC:是直接使用了JDBC 的提交和回滚设置;MANAGED:几乎什么都没做,只是让容器来管理事务的整个生命周期 |
dataSource | UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接;POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来;JNDI:容器可以集中或在外部配置数据 源,然后放置一个 JNDI 上下文的数据源引用 |
2、properties标签
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///mybatis_db
jdbc.username=root
jdbc.password=root
<properties resource="jdbc.properties"></properties>
该标签可加载外部的properties文件
3、typeAliases标签
<typeAlias type="com.lagou.domain.User" alias="user"></typeAlias>
别名标签,可以为Java设置一个简短的别名。
为了简化映射文件 Java 类型设置,mybatis框架已经配置一些常用类别名:string,long,int,double,boolean等。
4、Mapper标签
该标签可以以多种方式加载映射。
1、使用相对于类路径的资源引用
<mapper resource="org/mybatis/builder/userMapper.xml"/>
2、使用完全限定资源定位符(URL)
<mapper url="file:///var/mappers/userMapper.xml"/>
2.API概述
SqlSession的工厂构建起SqlSessionFactoryBuilder:通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象。
常用API:SqlSessionFactory build(InputStream inputStream)
SqlSession工厂对象SqlSessionFactory:SqlSessionFactory 有多个个方法创建SqlSession 实例 。常用两个方法 openSession()
空参和带参两种。空参默认开启一个事务但不会提交,需要手动提交,而带参的则设置自动提交事务。
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();
3.Dao层开发使用
1、编写dao接口
public interface UserDao{
public List<User> findAll() throws Exception;
}
2、继承dao接口并编写对应的impl类
public class UserDaoImpl implements UserDao{
@Override
public List<User> findAll() throws Exception {
// 加载配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
// 获取SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 获取SqlSe会话对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行sql
List<User> list = sqlSession.selectList("UserMapper.findAll");
// 释放资源
sqlSession.close();
return list;
}
}
3、配置 对应的xml
<mapper namespace="UserDao">
<select id="findAll" resultType="User">
select * from user
</select>
</mapper>
2.配置文件深入Result
1、mybatis高级查询
1.1 ResultMap属性
标签 | 说明 |
---|---|
resultType | 如果实体的属性名与表中字段名一致,将查询结果自动封装到实体类中 |
resultMap | 如果实体的属性名与表中字段名不一致,可以使用ResutlMap实现手动封装到实体类中 |
<resultMap id="userResultMap" type="user">
<id column="uid" property="id"/>
<result column="name" property="username"/>
...
</resultMap>
1.2 多条件查询
多条件查询存在三种方式:
- 使用
#{arg0}-#{argn}
或者#{param1}-#{paramn}
获取参数 - 使用注解,引入
@Param()
注解获取参数 - 使用POJO对象传递参数(推荐)
本篇中主要介绍使用POJO对象传递参数。
- 创建对应接口
public interface UserMapper {
public List<User> findByIdAndUsername3(User user);
}
- 创建对应xml映射文件
<mapper namespace="com.lagou.mapper.UserMapper">
<select id="findByIdAndUsername3" parameterType="com.lagou.domain.User" resultType="com.lagou.domain.User">
select * from user where id = #{id} and username = #{username}
</select>
</mapper>
1.3 模糊查询
模糊查询与多条件查询方式类似,创建对应的接口以及对应的xml映射文件,只是在查询语句中发生变化。
<mapper namespace="com.lagou.mapper.UserMapper">
<select id="findByUsername1" parameterType="string" resultType="user">
select * from user where username like #{username}
</select>
</mapper>
在使用${ }
和#{ }
时注意区分两者不同
${ } | #{ } |
---|---|
通过 #{} 可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。 | 通过 ${} 可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换,会出现sql注入问题。 |
如果parameterType传输单个简单类型值, #{} 括号中名称随便写。 | 如果parameterType传输单个简单类型值, ${} 括号中只能是value。 |
2.映射文件深入
2.1 返回主键值
2.1.1 useGeneratedKeys
注意:只适用于主键自增的数据库,mysql和sqlserver支持,oracle不行。
<!-- useGeneratedKeys="true" 声明返回主键
keyProperty="id" 把返回主键的值,封装到实体的id属性中-->
<insert id="save" parameterType="user" useGeneratedKeys="true" keyProperty="id">
2.1.2 selectKey
<!-- selectKey 适用范围广,支持所有类型数据库
keyColumn="id" 指定主键列名
keyProperty="id" 指定主键封装到实体的id属性中
resultType="int" 指定主键类型
order="AFTER" 设置在sql语句执行前(后),执行此语句 -->
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID();
</selectKey>
2.2 动态SQL
2.2.1 <if>
<if>
相当于在SQL查询中的条件查询,当条件不为空时后面添加条件,需要注意的是if判断须在<where>
内使用。
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="">
</if>
....
</where>
2.2.2 <set>
在使用<where>
时需要在判断条件之间插入,分隔条件;但如果条件是最后一个时,会导致以逗号结尾的情况,导致SQL语法错误。所以<set>
可以有效结局此类问题发生,当条件句为最后一句时会自动删除结尾的逗号,避免语法错误。
<set>
<if test="id != null">
AND id = #{id},
</if>
<if test="">
</if>
....
</set>
2.2.3<foreach>
主要是用来做数据的循环遍历
<!--
<foreach>标签用于遍历集合,它的属性:
• collection:代表要遍历的集合元素
• open:代表语句的开始部分
• close:代表结束部分
• item:代表遍历集合的每个元素,生成的变量名
• sperator:代表分隔符
-->
<where>
<foreach collection="collection" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
2.3 sql抽取
在编写sql语句的过程中会存在相同sql语句,可以将相同语句进行抽取,避免代码冗余。
<!--抽取的sql片段-->
<sql id="selectUser">
SELECT * FROM `user`
</sql>
3.mybatis核心配置文件深入
plugins标签
MyBatis可以使用第三方的插件来对功能进行扩展,PageHelper分页助手可以将sql查询结果分页
步骤:
- 导入通用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插件
<!-- 分页助手的插件 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 指定方言 -->
<property name="dialect" value="mysql"/>
</plugin>
在使用PageHelper.startPage(n,m);
时需要设置分页参数(开始数据条目,结束数据条目)。
4.多表查询
4.1 一对一查询
- 在实体类中添加对应的关系链表
private User user;
- 配置对应实体类接口
public List<Order> findAllWithUser();
3.配置对应映射文件
<resultMap id="orderMap" type="com.lagou.domain.Order">
<id column="id" property="id"></id>
<result column="ordertime" property="ordertime"></result>
<result column="money" property="money"></result>
<!--一对一(多对一)使用association标签关联
property="user" 封装实体的属性名
javaType="user" 封装实体的属性类型 -->
<association property="user" javaType="com.lagou.domain.User">
<id column="uid" property="id"></id>
<result column="username" property="username"></result>
<result column="birthday" property="birthday"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
</association>
</resultMap>
4.2 一对多
- 在实体类中添加对应的集合
private List<Order> orderList;
- 配置对应实体类接口
public List<User> findAllWithOrder();
- 配置对应映射文件
注: 当对应的时一个时使用的是<association>
标签,当对应的是多个的时使用的是<collection>
<resultMap id="userMap" type="com.lagou.domain.User">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="birthday" property="birthday"></result>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<!--一对多使用collection标签关联
property="orderList" 封装到集合的属性名
ofType="order" 封装集合的泛型类型 -->
<collection property="orderList" ofType="com.lagou.domain.Order">
<id column="oid" property="id"></id>
<result column="ordertime" property="ordertime"></result>
<result column="money" property="money"></result>
</collection>
</resultMap>
4.3 多对多
多对多查询相当于多个一对多查询,需要创建中间表进行表与表之间连接,其余的都和一对多查询相似。
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// 代表当前用户关联的角色列表
private List<Role> roleList;
}
public class Role {
private Integer id;
private String roleName;
private String roleDesc;
}
5.嵌套查询
嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用mybatis的语法嵌套在一起。
这里以一对一进行举例,其余不进行代码演示
-- 先查询订单
SELECT * FROM orders;
-- 再根据订单uid外键,查询用户
SELECT * FROM `user` WHERE id = #{订单的uid};
代码实现
- OrderMapper接口
public List<Order> findAllWithUser();
- OrderMapper.xml映射
<!--一对一嵌套查询-->
<resultMap id="orderMap" type="order">
<id column="id" property="id"></id>
<result column="ordertime" property="ordertime"></result>
<result column="money" property="money"></result>
<!--根据订单中uid外键,查询用户表-->
<association property="user" javaType="user" column="uid" select="com.lagou.mapper.UserMapper.findById"></association>
</resultMap>
<select id="findAllWithUser" resultMap="orderMap" >
SELECT * FROM orders
</select>
- UserMapper接口
public User findById(Integer id);
- UserMapper映射
<select id="findById" parameterType="int" resultType="user">
SELECT * FROM `user` where id = #{uid}
</select>
3.加载策略
1.延迟加载策略
概念:在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
优点:先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表 速度要快。
缺点:因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时 间,所以可能造成用户等待时间变长,造成用户体验下降。
在进行多表查询中,通常一对一和一对多会采用延迟加载策略,而多对多则会采取立即加载策略。
注意:延迟加载是基于嵌套查询来实现的。
实现方法
- 局部延迟加载
在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略。
fetchType="lazy" 懒加载策略
fetchType="eager" 立即加载策略
- 全局延迟加载
在Mybatis的核心配置文件中可以使用setting标签修改全局的加载策略。
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
4.注解开发
Mybatis也可以使用注解开发方式,这样我们就可以减少编写Mapper映射文件了。
常用注解:
注解 | 说明 |
---|---|
@Insert | 实现新增,代替了<insert></insert> |
@Delete | 实现删除,代替了<delete></delete> |
@Update | 实现更新,代替了<update></update> |
@Select | 实现查询,代替了<select></select> |
@Result | 实现结果集封装,代替了<result></result> |
@Results | 可以与@Result 一起使用,封装多个结果集,代替了<resultMap></resultMap> |
@One | 实现一对一结果集封装,代替了<association></association> |
@Many | 实现一对多结果集封装,代替了<collection></collection> |
使用注解进行增删改查
- 创建接口
public interface UserMapper {
@Select("SELECT * FROM `user`")
public List<User> findAll();
@Insert("INSERT INTO `user`(username,birthday,sex,address) VALUES(# {username},#{birthday},#{sex},#{address})")
public void save(User user);
@Update("UPDATE `user` SET username = #{username},birthday = #{birthday},sex = #{sex},address = #{address} WHERE id = #{id}")
public void update(User user);
@Delete("DELETE FROM `user` where id = #{id}")
public void delete(Integer id);
}
- 配置对应映射文件
<!--我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的Mapper接口即可-->
<mappers>
<!--扫描使用注解的Mapper类-->
<mapper class="com.lagou.mapper.UserMapper"></mapper>
<!--或者使用注解的类所在的包-->
<package name="com.lagou.mapper"></package>
</mappers>