1.了解ORM框架发展历史,了解MyBatis特性
2.掌握MyBatis编程式开发方法和核心对象
3.掌握MyBatis核心配置含义
4.掌握MyBatis的高级用法与扩展方式
java为了统一所有的数据库操作的接口,sun公司制定了一个jdbc的规范,让我们可以在java语言里使用相同的api去操作不同的数据库。
现在我以mysql为例实现jdbc连接数据库并操作
对应数据库表的实体类
public class Blog implements Serializable{
Integer bid; // 文章ID
String name; // 文章标题
Integer authorId; // 文章作者ID
public Integer getBid() {
return bid;
}
public void setBid(Integer bid) {
this.bid = bid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAuthorId() {
return authorId;
}
public void setAuthorId(Integer authorId) {
this.authorId = authorId;
}
@Override
public String toString() {
return "Blog{" +
"bid=" + bid +
", name='" + name + '\'' +
", authorId='" + authorId + '\'' +
'}';
}
}
JDBC操作数据库
public void testJdbc() throws IOException {
Connection conn = null;
Statement stmt = null;
Blog blog = new Blog();
try {
// 注册 JDBC 驱动
// Class.forName("com.mysql.jdbc.Driver");在后面的版本中打开链接部分会自动注册驱动
// 打开连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
// 执行查询
stmt = conn.createStatement();
String sql = "SELECT bid, name, author_id FROM blog where bid = 1";
ResultSet rs = stmt.executeQuery(sql);
// 获取结果集
while (rs.next()) {
Integer bid = rs.getInt("bid");
String name = rs.getString("name");
Integer authorId = rs.getInt("author_id");
blog.setAuthorId(authorId);
blog.setBid(bid);
blog.setName(name);
}
System.out.println(blog);
rs.close();
stmt.close();
conn.close();
} catch (SQLException se) {
se.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (stmt != null) stmt.close();
} catch (SQLException se2) {
}
try {
if (conn != null) conn.close();
} catch (SQLException se) {
se.printStackTrace();
}
}
}
分析
上面JDBC连接操作数据库必须要做的步骤
首先:在pom.xml中引入MYSQL驱动的依赖
- 第一步:Class.forName注册驱动
- 第二步:获取一个Connection 跟数据库之间的连接
- 第三步:创建一个Statement对象 执行SQL语句
- 第四步:execute()方法执行SQL,然后方法返回一个ResultSet结果集
- 第五步:通过ResultSet获取数据并封装到我们的POJO中
- 最后:关闭数据库相关的资源,包括ResultSet,Statement,Connection
如果我们项目中表比较的多或者项目复杂,对数据库的操作非常多且频繁的话,我们就需要不断的重复写这样的代码。
在每一段代码里都需要自己去管理数据库的连接资源,如果忘记了关闭资源就有可能造成数据库服务连接的耗尽。
还有就是对于结果集的处理,需要我们根据字段属性的类型一个一个的处理
总结:
- 重复代码
- 资源管理问题
- 结果集处理的繁琐
- SQL与代码耦合,维护复杂
这是原生的jdbc操作数据库的问题,因此Apache在203年的时候发布了一个Commons DbUtils的工具类来简化对数据库的操作
DbUtils提供了一个QueryRunner类,它对数据库的增删改查的方法进行了封装
在QueryRunner的构造函数里面,可以传入一个数据源,这样我们就不需要再去写各种创建和释放连接的代码,解决了资源管理问题
HikariConfig config = new HikariConfig(PROPERTY_PATH);
dataSource = new HikariDataSource(config);
queryRunner = new QueryRunner(dataSource);
那结果集的处理问题它有没有帮我们解决呢,答案是有的,在DbUtils里面提供了一系列的支持泛型的ResultSetHandler,可以用来把结果集转换成JavaBean,List,Map等。
我们只需要在dao层调用QueryRunner封装好的查询方法,传入一个指定了类型的Handler,底层是使用反射的方法,它就可以自动将结果集转换成我们想要转换的类型。
有一点要求是数据库的字段跟对象的属性名称完全一致才可以实现自动转换。
Spring JDBC
除了DbUtils之外,Spring也对原生的JDBC进行了封装。
那它是怎么解决原生JDBC的问题的呢?
Spring提供了一个模板方法JdbcTemplate,里面封装了各种各样的execute,query,update方法来解决代码重复问题
JdbcTemplate这个类是Jdbc核心包的中心类,简化了Jdbc的使用,它封装了Jdbc的核心流程,我们只需要提供Sql,提取结果集就可以了,并且它是线程安全的。
对于数据源的管理也提供了方法
对于结果集的处理,SpringJdbc提供了一个RowMapper接口,可以把结果集转换成java对象,作为JdbcTemplate的参数使用,我们只需要新建一个类实现RowMapper接口重写其mapRow()方法,在方法里完成对结果集的处理
这种方式可以使一张表的映射在项目中全局使用,减少重复,但是当表的数量大的时候,每张表都需要一个类去实现转换,这回导致文件数量的膨胀。
DbUtils和SpringJDBC对JDBC做了轻量级的封装,或许说是在工具类里帮助我们解决的问题有:
- 对数据库的增删改查的方法进行了封装;
- 解决了对数据库连接资源的管理,不再需要手动创建和关闭
- 帮助我们进行结果集的映射。
虽然这两个工具帮助我们解决了很多问题,但是还是存在不足:
1.SQL语句在代码里写死了,依旧存在硬编码问题
2.参数只能固定的顺序传入,不能实现对象或者集合去实现参数的自动映射
3.查询没有缓存的功能,性能不够出色
为了解决以上的所有问题,所以就出现了ORM框架
ORM定义:
ORM的全拼是Object Relation Mapping,也就是对象与关系的映射,对象时程序中的对象,关系是它与数据库里面的数据的关系。ORM框架为我们解决了程序对象和关系型数据库的相互映射的问题。
Hibernate是一个出色的ORM框架,它可以帮助我们自动的生成SQL语句,自动进行映射,让代码变得简洁和提高了可读性。
特点:
- 根据数据库方言自动生成SQL,一致性好
- 自动管理连接资源
- 实现对象与关系型数据库的完全映射
- 提供了缓存功能机制
不足:
在业务复杂的项目中会存在许多的问题
- 它里面自动生成的SQL都是对数据库的所有字段操作,没有办法指定部分字段,不够灵活
- 当我们需要基于sql做优化是,无法完成,
- 无法动态生成SQL
MyBatis
“半自动化”的ORM框架的MyBatis就可以解决以上的问题。它的封装程度没有Hibernate高,不会自动生成全部的SQL语句,主要解决的是SQL和对象的映射问题。
MyBatis的使用
先引入jar包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4-snapshot</version>
</dependency>
创建一个全局配置文件,这里面是对MyBatis的核心行为的控制,这里面定义数据源和Mapper引射器路径
<?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 resource="db.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
<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>
<mappers>
<mapper resource="BlogMapper.xml"/>
<mapper resource="BlogMapperExt.xml"/>
</mappers>
</configuration>
db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true
jdbc.username=root
jdbc.password=123456
然后编写我们的映射器文件:Mapper.xml,我们会在这里配置增删改查的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 namespace="com.myBatis.mapper.BlogMapper">
<resultMap id="BaseResultMap" type="blog">
<id column="bid" property="bid" jdbcType="INTEGER"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="author_id" property="authorId" jdbcType="INTEGER"/>
</resultMap>
<select id="selectBlogById" resultMap="BaseResultMap" statementType="PREPARED" >
select * from blog where bid = #{bid}
</select>
</mapper>
MyBatis提供了一个叫做SqlSession的对象来对数据库的操作,我们可以把它理解为跟数据库的一个连接,一个会话。
SqlSession的创建是基于全局配置文件的,因为MyBatis的核心行为都是在全局配置文件中,他不是直接new出来的,而是通过一个工厂类得到。
最后我们通过SqlSession接口上的方法传入我们的Statement ID(一个SQL语句的唯一标识,nameSpace加上SQL的id)来执行Mapper映射器中的SQL。
public class MyBatisTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void prepare() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
/**
* 使用 MyBatis API方式
* @throws IOException
*/
@Test
public void testStatement() throws IOException {
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = (Blog) session.selectOne("com.myBatis.mapper.BlogMapper.selectBlogById", 1);
System.out.println(blog);
} finally {
session.close();
}
}
}
通过以上的方式,解决了重复代码,资源管理,SQL耦合,结果集映射这四大问题。
上面的调用方式还是存在了一些问题:
- Statement ID是硬编码,维护起来很不方便
- 不能在编译时进行类型检查,如果Statement ID输错了,只能在运行时报错。
因此在新版的MyBatis里推荐了新的方式:定义一个Mapper接口的方式,接口的全路径名必须跟Mapper.xml里面的namespace对应起来,方法也必须跟Statement ID一一对应
调用方式:
@Test
public void testMapper() throws IOException {
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);//代理
Blog blog=mapper.selectBlogById(1);
System.out.println(blog);
} finally {
session.close();
}
}
在此先总结一下MyBatis的核心特性:
- 使用连接池对象进行连接
- SQL和代码分离,集中管理
- 结果集映射
- 参数映射和动态SQL
- 重复SQL的提取
- 缓存管理
- 插件机制
核心对象的生命周期
在使用MyBatis时我们可以发现在demo里面,有几个核心对象:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession和Mapper对象,这几个核心对象在MyBatis的整个工作流程里面的不同环节发挥作用。
在一些分布式的应用里面,多线程高并发的场景中,如果要写出高效的代码,了解这四个对象的生命周期是非常重要的。
1.SqlSessionFactoryBuilder
它是用来构建SqlSessionFactory的,而SqlSessionFactory只需要一个,所以只要构建了这一个SqlSessionFactory,它的使命就完成了,也就没有存在的意义了。所以它的生命周期只存在于方法的局部。
2.SqlSessionFactory(单例)
SqlSessionFactory是用来创建SqlSession的,每次应用程序访问数据库,都需要创建一个会话。因为我们一直有创建会话的需要,所以SqlSessionFactory应该存在于应用的整个生命周期中(作用域是应用的作用域)。创建SqlSession只需要一个实例来做这件事就行了,否则会产生很多的混乱,和浪费资源。所以我们要采用单例模式。
3.SqlSession
SqlSession是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们在请求开始的时候创建一个SqlSession
对象,在请求结束或者说方法执行完毕的时候要及时关闭它(一次请求或操作中)
4.Mapper
Mapper(实际上是一个代理对象)是从SqlSession中获取。
BlogMapper mapper=session.getMapper(BlogMapper.class);
它的作用是发送sql来操作数据库的数据。他应该在一个SqlSeesion事务方法之内。
总结:
SqlSessionFactoryBuilder 方法局部(method)
|
SqlSessionFactory 应用级别(application)
|
SqlSession 请求和操作(request/method)
|
Mapper 方法(method)
核心配置文件解读
第一个就是config文件。大部分时候我们只需要很少的配置就可以让MyBatis运行起来。MyBatis里面提供的配置项非常的多,我们没有显式的配置的时候,这些配置项使用的是系统的默认值。
一级标签
- configuration
configuration是整个配置文件的跟标签,,它贯穿MyBatis执行流程的每一个环节。 - properties
properties用来配置参数信息,比如常见的数据库连接信息。
setting
setting里是MyBatis的一些核心配置 - typeAliases
TypeAlias是类型的别名,用来简化类名全路径的拼写。 - typeHandlers
由于Java类型和数据库的类型不是一一对应的(比如String与varchar、char、text),所以我们把java对象转换为数据库的值,把数据库的值转换成java对象就需要经过一段的转换,者就需要用到TypeHandler。
关于更多配置的解读、映射文件和动态SQL语句在MyBatis的中文官网写得很明细。
https://mybatis.org/mybatis-3/zh/configuration.html