目录
1、持久层的概念和MyBatis的特点
持久层可以将业务数据存储到磁盘,具备长期存储能力,只要磁盘不损坏,或断电,或其他情况下,重新开启系统任然可以读取这些数据。
一般执行持久任务的都是数据库系统,持久层可以使用巨大的磁盘空间,也比较廉价,缺点就是比较慢。当然慢是针对内存而言的,在一般的系统中运行是不存在问题的,比如内存管理系统,但是在互联网的秒杀场景下,每秒都需要执行成千上万次数据操作,慢是不能承受的,极有可能宕机,这种情况下考虑使用Redis(NoSQL)处理它。
Java互联网应用通过MyBatis框架访问数据库:
MyBatis最大的成功主要有3点:
- 不屏蔽SQL,即可以更为精确地定位SQL语句,可以对其进行优化和改造,利于互联网系统性能优化
- 提供强大、灵活的映射机制,便于Java开发者使用。提供动态SQL的功能,允许我们根据不同条件组装SQL。
- 提供了使用Mapper的接口编程,只要一个Mapper接口和XML映射文件就能创建Mapper映射器,简化工作,减少框架API,使我们能更集中于业务逻辑。
2、准备MyBatis环境
一个讲解较入门的MyBatis参考文档:mybatis – MyBatis 3 | 入门
Maven仓库地址:https://mvnrepository.com/
MyBatis所需的包和源码下载地址:https://github.com/mybatis/mybatis-3/releases
3、MyBatis的核心组件
我们来看下MyBatis的“表面现象”——组件。
MyBatis的核心组件分为4部分:
- SqlSessionFactoryBuilder【构造器】:根据配置或程序来生成SqlSessionFactory,采用分步构建的Builder模式
- SqlSessionFactory【会话工厂接口】:依靠它来生成SqlSession,使用工厂模式
- SqlSession【会话】:既可以发送SQL执行返回结果,也可以获取Mapper的接口。一般我们会让其在业务逻辑程序中“消失”,而使用的是MyBatis提供的SQL Mapper接口编程技术,它能提高代码的可读性和可维护性。
- SQL Mapper【映射器】:由一个Java接口和XML文件(或注解)构成,需要给出对应的SQL和映射规则。映射器负责发送SQL去执行,并返回结果。
MyBatis核心组件之间的关系:
4、SqlSessionFactory(工厂接口)
使用MyBatis首先是使用配置或代码去生产SqlSessionFactory,而MyBatis提供了构造器SqlSessionFactoryBuilder。
它提供了一个类org.apache.ibatis.session.Cobfiguration作为引导,采用Builder模式。具体的分步是在Configuration类里面完成的。
在MyBatis中,既可以通过读取配置的XML文件的形式生产SqlSessionFactory,也可以通过Java代码的形式去生产SqlSessionFactory。这里建议使用XML配置的方式。当配置了XML或提供程序后,MyBatis会读取 配置文件,通过Configuration类对象构建整个MyBatis的上下文。
【注意】SqlSessionFactory接口存在两个实现类:SqlSessionManager和DefaultSqlSessionFactory。常用DefaultSqlSessionFactory去实现,而SqlSessionManager使用在多线程环境中,它的具体实现依靠DefaultSqlSessionFactory。它们之间关系:
每个基于MyBatis的应用都是一个以SqlSessionFactory的实例为中心的,而SqlSessionFactory唯一的作用就是生产MyBatis的核心接口对象SqlSession,所以它的责任是唯一的。这里,我们往往采用单例模式处理它,下面将讨论使用XML文件配置方式和Java代码方式去生产SqlSessionFactory的方法。
4.1、使用XML构建SqlSesionFactory
MyBatis的XML文件分为两类:
【1】基础配置文件
用于配置基本上下文参数和运行环境;
【2】映射文件
用于配置映射关系、SQL、参数等信息
MyBatis的基础配置文件:
<?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配置============================================== -->
<!--♠♠方式1: 引用java属性的文件db.properties -->
<properties resource="db.properties" />
<!--♠方式2: 在<properties>标签下的子元素<proper>定义数据库配置的name和value
如果属性参数有成千上百个,该方式不是最优的,建议使用方式1,通过java属性文件导入方式。
<properties>
<proper name="database.driver" value="com.mysql.jdbc.Driver"></proper>
<proper name="database.url" value="jdbc:mysql://localhost:3306/boot_security?characterEncoding=utf-8"></proper>
<proper name="database.username" value="root"></proper>
<proper name="database.password" value="root"></proper>
</properties>
-->
<!--♠♠♠方式3:代码传递方式传递参数
创建SqlSessionFactory时,将配置信息重置到properties属性中。
-->
<!-- ===========================================Setting设置================================================= -->
<!--
<settings>
====>开启驼峰规则与下划线间的映射关系
<setting name="mapUnderscoreToCamelCase" value="true"/>
====>设置jdbc与数据库的关系类型 mysql不需要配置 Oracle需要
<setting name="jdbcTypeForNull" value="NULL"/>
====>设置懒加载 优化速度 当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。默认值false
<setting name="lazyLoadingEnabled" value="true"/>
====>设置懒积极加载为消极加载 优化速度
<setting name="aggressiveLazyLoading" value="false"/>
====>该配置影响的所有映射器中配置的缓存的全局开关。默认值true
<setting name="cacheEnabled" value="true"/>
====>是否允许单一语句返回多结果集(需要兼容驱动)。 默认值true
<setting name="multipleResultSetsEnabled" value="true"/>
====>使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。默认值true
<setting name="useColumnLabel" value="true"/>
====>允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 默认值false
<setting name="useGeneratedKeys" value="false"/>
====>指定 MyBatis 应如何自动映射列到字段或属性。默认值PARTIAL
NONE 表示取消自动映射;
PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。
FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
<setting name="autoMappingBehavior" value="FULL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
====>配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。默认SIMPLE
<setting name="defaultExecutorType" value="SIMPLE"/>
====>设置超时时间,它决定驱动等待数据库响应的秒数。
<setting name="defaultStatementTimeout" value="25"/>
====>允许在嵌套语句中使用分页(RowBounds)默认值False
<setting name="safeRowBoundsEnabled" value="false"/>
====>允许在嵌套语句中使用分页(ResultHandler)默认值False
<setting name="safeResultHandlerEnabled" value="false"/>
====>MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
<setting name="localCacheScope" value="SESSION"/>
====>指定哪个对象的方法触发一次延迟加载。
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
-->
<!-- ========================================typeAliases别名设置============================================= -->
<!-- 定义实体类的别名:系统定义别名,或者自定义别名 -->
<!--方式1:定义单个POJO实体类的别名
引入POJO实体类的类全路径和别名后,其对应的映射文件中resultType返回结果的类型应当为alias定义的别名
type:实体类的全路径名称
alias:别名
<typeAlias alias="sysUser" type="com.mybatis.model.SysUser" /> -->
<typeAliases>
<!-- 按ID查询用户,使用单个POJO实体类别名方式定义,对需要起别名的类单独配置 -->
<typeAlias alias="sysUser" type="com.hl.magic.server.model.SysUser" />
<typeAlias alias="sysRole" type="com.hl.magic.server.model.SysRole" />
</typeAliases>
<!--方式2:使用“包扫描”的方式批量定义别名,定义后别名等于类名,不区分大小写,建议按照驼峰命名约束进行命名
可以配合mybatis的注解@Alias("xxx")进行区分,进而避免别名重名导致的扫描失败的问题
<typeAliases>
<package name="com.mybatis.pojo" />
</typeAliases>
-->
<!-- ===============================================数据库环境配置============================================= -->
<!-- 数据库环境-->
<environments default="development">
<environment id="development">
<!-- 使用JDBC事务管理器-->
<transactionManager type="JDBC" />
<!-- <transactionManager type="MANAGED" /> -->
<!-- 数据库连接池-->
<!-- <dataSource type="UNPOOLED">采用非数据库池的管理方式 -->
<!-- <dataSource type="JNDI"> -->
<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>
<!-- ==========================================mapper映射器配置=============================================== -->
<!-- Mapper映射器,加载核心映射配置文件 -->
<mappers>
<!--方式1:通过文件路径引入映射器
1-原始映射文件配置方式,映射文件的全路径,需要一一的书写,较繁琐
2-映射文件和Mapper接口可以不用放在一个目录下
-->
<mapper resource="mybatis-mappings/SysUserMapper.xml"/>
<mapper resource="mybatis-mappings/SysRoleMapper.xml"/>
<!--方式2:通过类注册方式引入映射器
mapper接口代理方式,使用class属性引入接口的全路径名称:
使用规则:
1-接口的名称和映射文件名称除扩展名外要完全相同
2-接口和映射文件要放在同一个目录下
<mapper class="com.mybatis.mapper.SysRoleMapper"/>
<mapper class="com.mybatis.mapper.SysUserMapper"/>
-->
<!--方式3:通过包扫描的方式批量引入映射器
使用规则:
1-接口的名称和映射文件名称除扩展名外要完全相同
2-接口和映射文件要放在同一个目录下
<package name="com.mybatis.mapper"/>
-->
<!--方式4:使用映射文件引入映射器
<mapper url="file:///var/mappers/mappers/SysRoleMapper.xml"/>
<mapper url="file:///var/mappers/mappers/SysUserMapper.xml"/>
-->
<!--方式5:通过@MapperScan包扫描的方式批量引入映射器
使用规则:
1-接口的名称和映射文件名称除扩展名外要完全相同
2-接口和映射文件要放在同一个目录下
@MapperScan("com.hl.magic.server.mapper")
-->
</mappers>
</configuration>
我们描述一下MyBatis的基础配置文件:
- <properties >:用于配置mybatis的属性文件,
方式1:引入外部的属性文件;
方式2:在其子标签<propertie>定义属性配置的name和value
方式3:代码传递方式传递参数
- <settings>:mybatis的基础功能的设置,基本上大多数配置如上程序中所示。
- <typeAliases>:定义了一个别名,它实际上与pojo类对应。这样定义后,mybatis上下文中就可以使用别名代替全限定名了
- <environments>:数据库描述。它里面的<transactionManager>元素是配置事务管理器,采用mybatis的JDBC管理方式。然后采用<dataSource>元素配置数据库,其中数学type="POOLED"代表采用mybatis内部提供的连接池方式,最后定义一些关于JDBC的属性信息。
- <mappers>:引入的映射器,加载核心映射配置文件。这里如上所示可采用多种方式配置映射器。
有了基础配置文件,我们用一段程序生成SqlSessionFactory:
SqlSessionFactory sqlSessionFactory = null;
String resource = "mybatisConfig.xml";
InputStream inputStream;
try{
//读取mybatisConfig.xml文件
inputStream = Resource.getResourceAsStream(resource);
//通过SqlSessionFactoryBuilder的build方法去创建SqlSessionFactory。
//采用Build模式隐藏了详细的构建细节
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch(IOException e){
e.printStackTrace();
}
采用XML创建的形式,信息在配置文件中,利于后期维护和修改,避免重新编译程序。
4.2、使用Java构建SqlSesionFactory
我们不建议使用这种方式去构建SqlSesionFactory,还是了解一下Java构建SqlSesionFactory。
//数据库连接池信息
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver(”com.mysql.jdbc.Driver”);
dataSource.setUsername(”root”);
dataSource.setPassword(”123456”) ;
dataSource.setUrl(”jdbc:mysql://localhost:3306/ssm”);
dataSource.setDefaultAutoCommit(false);
//采用MyBatis的JDBC事务方式
TransactionFactory transactionFactory = new JdbcTransactionFactory() ;
Environment environment = new Environment(”development” transactionFactory, dataSource);
//创建onf工guraton象
Configuration configuration = new Configuration(environment);
//注册一个MyBat工s上下文别名
configuration.getTypeAliasRegistry( ).registerAlias (”role", Role.class);
//加入一个映射器
configuration.addMapper(RoleMapper.class);
//使用SqlSessionFactoryBuilder构建SqlSessionFactory
SqlSessionFactory SqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration) ;
return SqlSessionFactory ;
它的功能和XML方式实现的功能是一致的,只是方式不一样。但是代码冗长,如果发生系统修改,有可能需要重新编译代码才能继续。
5、SqlSession(会话)
在MyBatis中,SqlSession是其核心接口。在MyBatis中有两个实现类:DefaultSqlSession和SqlSessionManager。
DefaultSqlSession是单线程使用的,而SqlSessionManager在多线程环境下使用。
SqlSession的作用类似一个JDBC中的Connection对象,代表着一个连接资源的启用。
具体而言,SqlSession的作用有3个:
- 获取Mapper接口
- 发生SQL与数据库
- 控制数据库事务
创建SqlSession:
SqlSession sqlSession = sqlSessionFactory.openSession();
注意SqlSession只是一个门面接口,在MyBatis中,真正在干活的是Executor。
SqlSession控制数据库事务的方法:
//定义SqlSession
SqlSession sqlSession = null;
try{
//打开SqlSession会话
sqlSession = SqlSessionFactory.openSession();
//....
sqlSession.commit();//提交事务
} catch(Exception e){
sqlSession.rollback();//回滚事务
} finally{
if(null != sqlSession){
sqlSession.close();
}
}
这里使用commit提交事务,或rollback回滚事务。因为SqlSession代表着一个数据库的连接资源,使用后要及时关闭它,如果不关闭,则数据库链接资源将会很快耗费光,影响系统性能。
6、映射器
映射器是MyBatis中最重要、最复杂的组件,它由一个Mapper接口和对应的XML文件(或注解)组成。它可以配置以下内容:
- 提供描述规则
- 提供SQL语句,并配置SQL参数类型、返回值类型、缓存刷新等信息
- 配置缓存
- 提供动态SQL
这里我们使用两种实现映射器的方式:【1】XML配置方式;【2】注解方式
定义一个POJO类:
public class SysRole {
private Integer id;
private String name;
private String description;
private Date createTime;
private Date updateTime;
/*省略setter和getter方法*/
}
映射器的主要作用就是讲SQL查询到的结果映射为一个POJO类,或将POJO的数据插入到数据库中,并定义一些关于缓存等。这里需要注意所定义的POJO类中变量需要与数据库中表的字段相对应。
6.1 用XML实现映射器
使用XML方式定义映射器包括:Mapper接口和XML配置文件。
首先定义一个映射器接口:
/**
* 映射器接口
*/
public interface SysRoleMapper {
/*===========================[1]最基础的操作===========================*/
/*[1.1]新增一条角色------->自定义主键*/
public int insertRole(SysRole sysRole);
/*[1.2]新增一条角色------->自增主键*/
public int insertRole2(SysRole sysRole);
/*[2]删除一条角色*/
public int deleteRole(Integer id);
/*[3]修改一条角色*/
public int updateRole(SysRole sysRole);
/*[4]查询一条角色*/
public SysRole getRole(Integer id);
/*===========================[2]传递多个参数===========================*/
/*[1]通过角色名获取整个角色列表*/
public List<SysRole> findRolesByMap(Map<String, Object> map);
/*
*[2]注解方式传递多个参数
* 注解@Param(),定义映射器的参数名称
* 传递参数的个数<=5时,最贱传参方式
* 传递参数的个数>5时,不推荐使用,建议使用Java Bean的方式
*/
public List<SysRole> findRolesByAnnotation(@Param("name") String name,
@Param("description") String description);
/*
*[3]Java Bean的方式传递多个参数
*/
public List<SysRole> findRoleByBean(SysRole sysRole);
/*
*[4]混合使用的方式传递多个参数
*可通过角色名称和描述进行查询,同时还需要支持分页
*须明确参数的合理性
*/
public List<SysRole> findRoleByMix(@Param("name") String name,
@Param("page") PageParams pageParams);
/*测试分页参数RowBounds*/
public List<SysRole> findRoleByRowBounds(@Param("name") String name,
@Param("description") String description, RowBounds rowBounds);
}
在mybatis的基础配置文件mybatisConfig.xml中,如下这个代码段所示:引入了映射器配置,它的目的就是引入Mapper接口对应的XML映射文件。
<!-- ==========================================mapper映射器配置=============================================== -->
<!-- Mapper映射器,加载核心映射配置文件 -->
<mappers>
<!--方式1:通过文件路径引入映射器
原始映射文件配置方式,映射文件的全路径,需要一一的书写,较繁琐
映射文件和Mapper接口可以不用放在一个目录下
-->
<mapper resource="mappers/SysRoleMapper.xml"/>
<!--方式2:通过类注册方式引入映射器
mapper接口代理方式,使用class属性引入接口的全路径名称:
使用规则:
1. 接口的名称和映射文件名称除扩展名外要完全相同
2. 接口和映射文件要放在同一个目录下
<mapper class="com.mybatis.mapper.SysRoleMapper"/>
<mapper class="com.mybatis.mapper.SysUserMapper"/>
-->
<!--方式3:通过包扫描的方式批量引入映射器
使用规则:
1. 接口的名称和映射文件名称除扩展名外要完全相同
2. 接口和映射文件要放在同一个目录下
<package name="com.mybatis.mapper"/>
-->
<!--方式4:使用映射文件引入映射器
<mapper url="file:///var/mappers/mappers/SysRoleMapper.xml"/>
<mapper url="file:///var/mappers/mappers/SysUserMapper.xml"/>
-->
</mappers>
在基础配置文件中,引入mybatis的映射器后,我们需要使用XML文件来创建一个映射器,实际上这个映射文件和Mapper接口相对应,主要用于通过SQL语句来操作数据库 :
<?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动态代理方式实现mybatis配置,使用mybatis接口、对应的ml映射文件、model实体类
namespace:命名空间[接口类的全路径], 作用:做sql隔离
Mapper接口代理实现编写规则:
1.映射文件中namespace名等于Mapper接口类的全路径;
2.映射文件中SQL语句要等于Mapper接口类的方法名称
3.映射文件中传入参数类型要等于Mapper接口类中方法的传入参数类型
4.映射文件中返回结果集类型要等于Mapper接口类中方法的返回类型
-->
<mapper namespace="com.mybatis.mapper.SysRoleMapper">
<!-- ===============================================新增=============================================== -->
<!--insert:新增/插入数据,=============================自定义主键=============================
id:一般对应接口中定义的SQL方法名称;
parameterType:指定传入的参数类型;
子元素selectKey:支持自定义键值的生成规则。
keyProperty:将返回的主键放入传入参数的Id中保存【指定采用哪个属性作为POJO的主键】;
order:当前函数相对于SQL语句的执行顺序,在insert前执行的是BEFORE,在insert后执行的是AFTER
由于mybatis的自增原理执行完SQL语句后才将主键生成,所以使用selectKey的执行顺序为AFTER
resultType:返回的主键id的类型,也就是keyProperties中id属性的类型
resultMap:用来描述从数据库结果集中来加载对象,它是最复杂、强大的元素【提供映射规则】
如果要返回数据库自增主键:可以使用select LAST_INSERT_ID()
执行 select LAST_INSERT_ID()数据库函数,返回自增的主键
LAST_INSERT_ID():MySQL的函数,返回auto_increment自增列新记录id的值
#{}占位符:如果传入的是pojo实体类型,占位符中的变量名称必须是pojo实体类中对应的属性.属性.属性...
-->
<insert id="insertRole" parameterType="sysRole">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
insert into sys_role(name,description,createTime,updateTime)
values (#{name}, #{description}, #{createTime},#{updateTime})
</insert>
<!-- insert:新增/插入数据,=============================自增主键=============================
useGeneratedKeys:获取数据库生成的主键,如果存在多个主键,就用逗号隔开
-->
<insert id="insertRole2" parameterType="sysRole" useGeneratedKeys="true" keyProperty="id">
insert into sys_role(name,description)
values (#{name}, #{description})
</insert>
<!-- ===============================================删除=============================================== -->
<!-- 按ID删除,parameterType传入参数类型=========>POJO类中角色关联表对应的id字段参数的类型 -->
<delete id="deleteRole" parameterType="java.lang.Integer">
delete from sys_role where id = #{id}
</delete>
<!-- ===============================================修改=============================================== -->
<update id="updateRole" parameterType="sysRole">
update sys_role set name = #{name},description = #{description}
where id = #{id}
</update>
<!-- ===============================================查询=============================================== -->
<!--<select>标签
id:sql语句唯一标识,一般为Mapper接口类中的查询方法名
parameterType:指定SQL方法传入的参数类型(或者指定传入的参数类型)
resultType:指定返回结果集的类型,【常设置为实体类的全路径,返回指向的model类型】
#{}占位符:起到占位作用,如果传入的是基本类型(string,long,double,int,boolean,float等),那么#{}中的变量名称可以随意写。
resultType="sysRole",全局配置文件中,使用单个定义别名,也可以使用model实体类的全路径,与核心配置文件中<typeAlias>标签中的alias命名一致
flushCache:调用SQL后,是否要求mybatis清空之前查询本地缓存和二级缓存,默认false
useCache:启动二级缓存开关,是否将本次结果缓存,默认false
-->
<!-- ============================================sql元素============================================== -->
<!-- 只需编写一次,就可以在其他元素中引用它了 -->
<sql id="roleCols">
id,name,description
</sql>
<select id="getRole" parameterType="java.lang.Integer" resultType="sysRole" flushCache="true">
select id,role_name as roleName from sys_role where id = #{id}
<!-- ==========>>>>>使用sql元素<<<<<========== -->
<!-- select <include refid="roleCols" /> from sys_role where id = #{id} -->
</select>
<!-- ================================================================================================ -->
</mapper>
【注意】我们并没有配置SQL执行后和role的对应关系,它是如何映射的呢?其实这里采用的是一种被称为自动映射的功能,MyBatis在默认情况下提供自动映射,只要SQL返回的列名能和POJO对应起来即可。这里SQL返回的列名id和note是可以和之前定义的POJO的属性对应起来的,而表里的列role_name通过SQL别名的改写,使其成为roleName,也是和POJO对应起来的,所以此时MyBatis就可以把SQL查询的结果通过自动映射的功能映射成为一个POJO。
6.2 用注解实现映射器
使用注解方式实现映射器,只需一个接口即可,可以通过MyBatis的注解来注入SQL。
publis interface SysRoleMapper2{
@Select("select id,role_name as roleName from sys_role where id = #{id}")
public SysRole getRole(int id);
}
注解方式等同于XML方式创建映射器,但是在复杂的SQL语句上,注解方式的缺点就暴露出来,即注释内容过多而影响代码可读性。如果使用动态SQL,判断字段空作为查询条件,需要加入其他逻辑,使得注解更为复杂,不利后期维护和修改。