[小白进阶日记]MyBatis入门-进阶教程
前言
MyBatis 是一个持久层的框架,它的前身是iBatis。 现在在GitHub管理
问题:
1. MyBatis开发 相比于 原生JDBC开发 有什么优点?
2. 如何使用MyBatis进行开发?
入门部分
首先我们先来回顾一下原生JDBC的开发步骤:
0. 导入Jar包
1. 注册驱动(Class.forName("com.mysql.jdbc.Driver") 方式不唯一)
2. 创建并获取连接(DriverManager.getConnection(url,username,password))
3. 获取执行对象statement(这里使用预编译 connection.prepareStatement(sql))
4. 执行sql 如果是查询 处理结果集 ResultSet
5. 释放资源 resultset、prepareStatement、connection
那么这会带来什么问题呢?(MyBatis将解决这些问题)
- 数据库连接频繁的 的创建、销毁造成系统资源的浪费。
解决方案:建立数据库连接池。 - SQL语句是代码中的硬编码,造成代码不易维护,如果需要更改需要进行源码修改。
解决方案:将SQL代码全部编写入配置文件,修改只需要修改配置文件 - 使用preparedStatement向有占位符好传参存在硬编码(参数为止、参数)问题,造成代码维护不易。
解决方案:将SQL中的占位符对应的数据类型存入配置文件,能够自动输入映射。 - 遍历查询结果集存在硬编码(列名)
解决方案:自动进行SQL查询结果向Java对象的映射 也就是输出映射
那么进行MyBatis的开发步骤是怎样的呢?
0. 导入Jar包
1. 编写核心配置文件(SqlMapConfig.xml)
2. 编写Mapper.xml 配置文件
3. 通过配置文件创建SqlSessionFactory
4. 通过SqlSessionFactory获取SqlSession
5. 通过SqlSession操作数据库 (增删改操作需要 commit )
6. 关闭SqlSession
导入MyBatis 的 Jar 包和依赖包
asm-3.3.1.jar
cglib-2.2.2.jar
commons-logging-1.1.1.jar
javassist-3.17.1-GA.jar
log4j-1.2.17.jar
log4j-api-2.0-rc1.jar
log4j-core-2.0-rc1.jar
mybatis-3.2.7.jar
slf4j-api-1.7.5.jar
slf4j-log4j12-1.7.5.jar
编写核心配置文件 SqlMapConfig.xml
需要注意的是 编写时是有标签顺序的(自上而下)
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)—》mybaties有一个分页插件
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
<?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->settings->typeAliases
->typeHandlers->objectWrapperFactory
->plugins->environments->databaseIdProvider
->mappers
-->
<!-- 读取jdbc.properties 文件 -->
<properties resource="jdbc.properties"></properties>
<settings>
<setting name="" value=""/>
</settings>
<!-- 为类型配置别名 -->
<typeAliases>
<!--第一种方式 type写类型全路径名 alias写别名 -->
<typeAlias type="" alias=""></typeAlias>
<!--第二种方式 扫描包,为该包下所有类添加别名,默认是类名 -->
<package name=""/>
</typeAliases>
<!-- default 默认的环境ID
development:开发模式
work:工作模式
作用是选择未指定情况下的环境ID(environment 的 ID)-->
<environments default="development">
<!--环境-->
<environment id="development">
<!--
配置事务管理器
type :JDBC|MANAGED
JDBC:直接使用JDBC 的事务提交和回滚设置,依赖从数据源(dataSource)获
取的 连接 来管理事务范围。
MANAGED:这个配置从来都不提交和回滚一个连接,而是让容器来管理事务的整个
生命周期(比如JEE应用服务的上下文)。默认情况下他会关闭连接,然而一些容
器并不希望这样,因此需要将closeConnection属性设置为false来阻止它默认的
关闭行为。
如果使用了 Spring + MyBatis 则没有必要配置事务管理器, 因为 Spring 模块
会使用自带的管理器来覆盖前面的配置。
-->
<transactionManager type="JDBC"></transactionManager>
<!--
配置数据源
type:UNPOOLED | POOLED | JNDI
UNPOOLED:这个数据源实现的只是每次请求时打开和关闭连接。
POOLED: 这个数据源实现了连接池的概念,避免了创建新连接初实例时的始化和认证
的时间。是使WEB应用快速响应请求的流行处理方式。
JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以
集中或在外部配置数据源,然后放置一个 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}"/>
<property name="dedaultTransactionIsolationLevel" value=""/>
</dataSource>
</environment>
</environments>
<!--映射器-->
<mappers>
<!--第一种方式通过 resource填写类路径的资源引用,或者通过url 填写完全限定资源定位费包括file:的url-->
<mapper resource="" url=""></mapper>
<!--第二种方式 扫描包,会将包下的配置文件读取-->
<package name=""></package>
</mappers>
</configuration>
编写接口和Mapper
public interface StudentMapper {
List<Student> findAll();
void delById(Integer id);
void save(Student student);
void updateById(Student student);
}
编写StudentMapper.xml
namespace 的两种用法
第一种 随意命名 不推荐 原生Dao开发 使用时用sqlSession进行增删改查操作
第二种 接口全限定名 推荐 Mapper代理开发,接口中的方法名和返回类型要和id resultType/resultMap对应,使用代理后的接口进行增删改查
在这里我们使用了Mapper代理的方式
<?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.baidu.service.StudentMapper">
<!--查询方法-->
<select id="findAll" resultType="com.baidu.pojo.Student">
select * from t_student
</select>
<!--删除方法-->
<delete id="delById" parameterType="int">
delete from t_student where id=#{id}
</delete>
<!--添加方法-->
<insert id="save" parameterType="com.baidu.pojo.Student">
insert into t_student set id=#{id},name=#{name}
</insert>
<!--修改方法-->
<update id="updateById" parameterType="com.baidu.pojo.Student">
update from t_student set name=${name} where id=#{id}
</update>
</mapper>
编写测试Dome
public class TestDome01 {
public static void main(String[] args) throws IOException {
// 获取SqlMapConfig.xml配置文件的读取流
InputStream resource = Resources.getResourceAsStream("SqlMapConfig.xml");
// 通过SqlSessionFactoryBuilder().builder(InputStream in) 来构建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resource);
// 通过SqlSessionFactory获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 使用Mapper代理的方式进行创建代理对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 执行方法
mapper.findAll();
// 通过原生Dao的形式进行调用 Mapper中的 nameScope 点 方法的ID
List<Student> studentList = sqlSession.selectList("test.findAll");
}
}
接下来要写的是MyBatis中的 动态SQL 和 SQL片段
在很多时候我们都会遇到一些情况 比如:SQL中查询条件不固定、SQL代码重复写等
那么动态SQL和SQL片段就可以解决这些问题
首先我们先来了解一下动态SQL
什么是动态SQL呢?动态SQL标签都有哪些?
动态SQL :在不确定参数数量时使用一些MyBatis提供的标签来运行。
举个小例子 我们想查寻学生,但是我们不确定要根据多少个ID来查多少学生,也不确定是不是要查询所有学生,所以动态SQL在这里就发挥出了它的作用。
<select id="findById" parameterType="com.baidu.pojo.Student" resultType="com.baidu.pojo.Student">
select * from t_student
<where>
<if test="ids!=null">
<foreach open="id in(" close=")" separator="," collection="ids" item="i">
#{i}
</foreach>
</if>
</where>
</select>
然后我们在来看一下SQL片段
SQL片段出现的目的是为了让开发者减少重复书写相同代码
由于我们要对一张表进行各种各样的条件查询那么前面的 select * from t_student 是会一直重新写只有条件不同所有我们把这段代码抽取出来封成一个SQL片段 需要时就 include 一下 这样可以大大省略我们在写sql时重复编写相同sql的时间
<sql id="selectAllStudent">
select * from t_student
</sql>
<select id="findById" parameterType="com.baidu.pojo.Student" resultType="com.baidu.pojo.Student">
<include refid="selectAllStudent"/>
<where>
<if test="ids!=null">
<foreach open="id in(" close=")" separator="," collection="ids" item="i">
#{i}
</foreach>
</if>
</where>
</select>
返回主键的两种方式
- 方法一 通过slectKey的形式
<insert id="save" parameterType="com.baidu.pojo.Student">
<!-- keyProperty 为那个属性进行赋值,resultType 返回的类型 order 是SQL执行前执行还是SQL执行后执行-->
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
insert into t_student set id=#{id},name=#{name}
</insert>
- 方法二 通过
进阶部分
在这里我们会介绍以下内容并附Dome解释。
- resultMap高级映射
- MyBatis缓存
- 延迟加载
- MyBatis逆向工程
ResultMap 高级映射
如果我们在查询时出现了 表中字段和定义的Bean(Pojo)中字段不符合,或者出现一对一、一对多的多表情况,那么我们就需要进行高级映射。下面我们一起来详细分析一下吧。
表结构
Pojo
- 表中字段不符
当出现表中字段和Pojo中的字段不符合时我们可以通过ResultMap来进行映射
比如我们查询的Student表中的字段名或者别名分别是:s_id,s_name Pojo中的属性是:id,name 那么这时MyBatis使用ResultType已经没有办法为我们封装好这个对象了,那么我们该怎么解决这个问题呢?下面我们就使用resultMap来决绝这个问题
在resultMap中<resultMap id="t_student" type="com.baidu.pojo.Student"> <id column="s_id" property="id"></id> <result column="s_name" property="name"></result> </resultMap> <select id="findAll" resultMap="t_student"> select id s_id,name s_name from t_student </select>
标签对应的是表中主键
标签中属性: column:是指表中字段名或查询出来的别名 property:对应的是Pojo中的属性名
标签对应的是表中普通字段
标签中的属性:和id标签的属性相同 - 多表查询
- 一对一 (学生 对 班级 一对一)
在实现一对一多表查询的方法有两种实现方式- 在Pojo中将另一个关联表中所需字段加入Pojo对象
增强Pojo 或者修改原有Pojo - 在ResultMap中使用标签
<resultMap id="studentANDBanji" type="com.baidu.pojo.Student"> <id column="id" property="id"></id> <result column="name" property="name"></result> <association property="banji" javaType="com.baidu.pojo.Banji"> <id column="bid" property="bid"></id> <result column="bname" property="bname"></result> </association> </resultMap> <select id="findStudentANDBanji" resultMap="studentANDBanji"> select s.*,b.bname from t_student s,t_banji b where s.bid=b.bid </select>
- 在Pojo中将另一个关联表中所需字段加入Pojo对象
- 一对多(班级 对 学生 是一对多)
实现一对多我们需要在ResultMap中使用标签 由于是一对多所以我们需要使用 ofType属性<resultMap id="banjiAndStudent" type="com.baidu.pojo.Banji"> <id property="bid" column="bid"></id> <id property="bname" column="bname"></id> <collection property="studentList" ofType="com.baidu.pojo.Student"> <id property="id" column="id"></id> <id property="name" column="name"></id> </collection> </resultMap> <select id="findAll" resultMap="banjiAndStudent"> select s.*,b.* from t_student s,t_banji b where s.bid=b.bid; </select>
- 一对一 (学生 对 班级 一对一)
MyBatis 持久层缓存
MyBatis提供了两个缓存 一级缓存 和 二级缓存
在MyBatis中 一级缓存是SqlSession级别的,不同的SqlSession不能互相访问缓存中的数据,
二级缓存是跨SqlSession的是Mapper级别的
一级缓存
缓存原理:第一次发出查询sql,sql查询的结果写入SqlSession的一级缓存中,缓存的数据结构是个map<k,v>的格式,k 是 hashcode+sql+输入的参数+输出参数(是唯一的标识),v用户查询到的信息,同一个SqlSession在次发出查询sql,如果与缓存中查询的sql相同,那么就会在缓存中取数据,而不会进行数据库查询,如果两次查询中出现了修改操作 也就是 commit ,那么缓存中的信息会被全部清空。
二级缓存
缓存原理:二级缓存的数据结构和一级缓存一样是map<k,v>的形式,每次查询时都会进行判断有没有开启二级缓存,如果开启了那么就会在二级缓存中取数据。
执行过程:先在二级缓存中找数据,如果没有去一级缓存找,还是没有那么从数据库查询。
同样执行了 commit 会将缓存清空
缓存配置:
向SqlMapConfig.xml中加入
然后在Mapper.xml中加入
由于二级缓存 需要数据落地(存入磁盘) 说以我们需要为Pojo实现Serializable接口 进行序列化和反序列化
延迟加载
听到延迟加载我想大家应该都有点蒙,为什么要延迟加载,他有什么好处?
首先在进行数据查询时,为了提高数据库查询的性能,尽量使用单表查询,因为多表查询会产生笛卡尔积,所以单表查询要比多表查询速度要快。
配置延迟加载
首先我们需要在SqlMapConfig.xml 中进行配置
<settings>
<!--全局懒加载-->
<setting name="lazyLoadingEnable" value="true"/>
<!--全局懒加载true被任何懒属性加载 false按需加载-->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
那么我们写一个小例子来看一下延迟加载到底是写在什么地方的
首先 懒加载是写在ResultMap中的
BanjiMapper.xml
<select id="findBanjiById" parameterType="int" resultType="com.baidu.pojo.Banji">
select * from t_banji where id=#{id}
</select>
StudentMapper.xml
<resultMap id="student" type="com.baidu.pojo.Student">
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<association property="banji" select="com.baidu.mapper.BanjiMapper.findBanjiById" column="bid"></association>
</resultMap>
<select id="findStudent" resultMap="student">
select * from t_student
</select>
我们在StudentMapper.xml 中的 findStudent方法中我们定义了返回的类型是resultMap 在student里我们定义了标签,在里面我们 使用了 select 属性调用了 BanjiMapper.xml中的方法 使用column传递给他一个bid,我们在SqlMapConfig.xml中设置了 懒加载 所以这时我们调用这个findStudent 就会进行慢加载,用到banji时才会查询banji对象。
MyBatis 逆向工程
mybatis需要程序员自己去编写sql语句,mybatis官方提供逆向工程,可以针对单表自动生成mybatis执行所需要的代码(mapper.java、mapper.xml、pojo…),可以让程序员将更多的精力放在繁杂的业务逻辑上。
下载地址
XML的配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="mysqlTable" targetRuntime="MyBatis3">
<!-- 1.数据连接参数 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/数据库"
userId="root"
password="root">
</jdbcConnection>
<!-- 2.默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL
和 NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- 3.生成模型的位置 -->
<javaModelGenerator targetPackage="com.gyf.backoffice.domain" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 4.targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="com.gyf.backoffice.mapper" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- 5. targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.gyf.backoffice.mapper"
targetProject=".\src">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 6.要生成的表 -->
<table tableName="t_student"/>
<table tableName="t_banji"/>
</context>
</generatorConfiguration>
Java代码
public class Generator {
public static void main(String[] args) throws Exception{
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("config/generator.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,callback, warnings);
myBatisGenerator.generate(null);
}
}