MyBatis 简介
MyBatis 官方文档: https://mybatis.org/mybatis-3/zh/index.html
原始 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 快速入门 MyBatis 开发步骤
添加 MyBatis 的坐标
创建 User 数据表
编写 User 实体类
编写映射文件 UserMapper.xml
编写核心文件 SqlMapConfig.xml
编写测试类
添加坐标 <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.5</version > </dependency >
创建数据表 创建实体类 package com.vsneko.domain;public class User { private int id; private String name; private int age; public int getId () { return id; } public void setId (int id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } @Override public String toString () { return "User{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + '}' ; } }
编写映射文件 在 resources
目录下创建配置文件 com/vsneko/mapper/UserMapper.xml
<?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 ="UserMapper" > <select id ="selectAll" resultType ="com.vsneko.domain.User" > select * from user </select > </mapper >
编写核心配置文件 在 resources
目录下创建配置文件 SqlMapConfig.xml
<?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 > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC" /> <property name ="username" value ="root" /> <property name ="password" value ="1025" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="com/vsneko/mapper/UserMapper.xml" /> </mappers > </configuration >
编写测试代码 @Test public void test1 () throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); List<User> userList = sqlSession.selectList("UserMapper.selectAll" ); System.out.println(userList); sqlSession.close(); }
MyBatis 的数据库基本操作 MyBatis 事务默认是不提交的,执行更新操作之后要提交事务。(或者在调用 openSession() 方法时传入 true)
查询操作 <select id ="selectAll" resultType ="com.vsneko.domain.User" > select * from user </select >
插入操作 <insert id ="insertUser" parameterType ="com.vsneko.domain.User" > insert into user values(#{id}, #{name}, #{age}) </insert >
修改操作 <update id ="updateUser" parameterType ="com.vsneko.domain.User" > update user set name=#{name} and age=#{age} where id=#{id} </update >
删除操作 如果传入的参数只有一个,那么在表达式里面写任意文本都可以,但要注意可读性。
<delete id ="deleteUser" parameterType ="java.lang.Integer" > delete from user where id=#{id} </delete >
MyBatis 核心配置文件 层级关系
environments 标签 <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC" /> <property name ="username" value ="root" /> <property name ="password" value ="1025" /> </dataSource > </environment > </environments >
transactionManager 事务管理器 transactionManager
类型有两种
JDBC: 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
MANAGED:这个配置几乎没做什么,它从来不提交或者回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection
属性设置为 false 来阻止它默认的关闭行为。
dataSource 数据源 dataSource
类型有三种
UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接
POOLED:这种数据源的实现利用 “池” 的概念将 JDBC 连接对象组织起来
JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中在外部配置数据源,然后放置一个 JNDI 上下文的引用
mappers 标签 <mappers > <mapper resource ="com/vsneko/mapper/UserMapper.xml" /> </mappers >
mapper mapper 标签的作用是加载映射的,加载方式有如下几种:
使用相对于类路径的资源引用
<mapper resource ="com/vsneko/mapper/UserMapper.xml" />
使用完全限定资源定位符(URL)
<mapper url ="file:///C:/UserMapper.xml" />
使用映射器接口实现类的完全限定类名
<mapper class ="com.vsneko.builder.UserMapper" />
将包内的映射器接口实现全部注册为映射器
<package name ="com.vsneko.builder" />
properties 标签 实际开发中,习惯将数据源的配置信息单独抽取成一个 properties 文件,该标签可以加载额外配置的 properties 文件。
<properties resource ="jdbc.properties" /> <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <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 标签 类型别名 typeAliases
是为 Java 类型设置一个短的名字。
<typeAliases > <typeAlias type ="com.vsneko.domain.User" alias ="user" /> </typeAliases >
这样,在编写映射文件的时候,参数类型可以直接写别名。
<insert id ="insertUser" parameterType ="user" > insert into user values(#{id}, #{name}, #{age}) </insert >
MyBatis 框架已经为我们设置好了一些常用的类型别名。
数据类型
别名
String
string
Long
long
Integer
int
Double
double
Boolean
boolean
…
…
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 类型。
开发步骤:
定义转换类,继承 BaseTypeHandler
覆盖 4 个未实现的方法,其中 setNonNullParameter 为 Java 程序设置数据到数据库的回调方法,getNullableResult 为查询时 mysql 的字符串类型转换成 java 的 Type 类型方法。
在 MyBatis 核心配置文件中进行注册
测试转换是否正确
package com.vsneko.handle;import org.apache.ibatis.type.BaseTypeHandler;import org.apache.ibatis.type.JdbcType;import java.sql.CallableStatement;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;import java.util.Date;public class DateTypeHandler extends BaseTypeHandler <Date > { public void setNonNullParameter (PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException { long time = date.getTime(); preparedStatement.setLong(i, time); } public Date getNullableResult (ResultSet resultSet, String s) throws SQLException { long time = resultSet.getLong(s); return new Date(time); } public Date getNullableResult (ResultSet resultSet, int i) throws SQLException { long time = resultSet.getLong(i); return new Date(time); } public Date getNullableResult (CallableStatement callableStatement, int i) throws SQLException { long time = callableStatement.getLong(i); return new Date(time); } }
<typeHandlers > <typeHandler handler ="com.vsneko.handle.DateTypeHandler" /> </typeHandlers >
plugins 标签 MyBatis 可以使用第三方的插件来对功能进行扩展,分页助手 PageHelper
是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。
开发步骤:
导入通用 PageHelper 的坐标
在 MyBatis 核心配置文件中配置 PageHelper 插件
测试分页数据获取
<dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper</artifactId > <version > 5.1.2</version > </dependency > <dependency > <groupId > com.github.jsqlparser</groupId > <artifactId > jsqlparser</artifactId > <version > 1.4</version > </dependency >
<plugins > <plugin interceptor ="com.github.pagehelper.PageInterceptor" > <property name ="helperDialect" value ="mysql" /> </plugin > </plugins >
PageHelper.startPage(1 , 2 ); List<User> users = userMapper.selectAll(); PageInfo<User> pageInfo = new PageInfo<User>(users); System.out.println("当前页: " + pageInfo.getPageNum()); System.out.println("每页数量: " + pageInfo.getPageSize()); System.out.println("总数量: " + pageInfo.getTotal()); System.out.println("总页数: " + pageInfo.getPages()); System.out.println("上一页: " + pageInfo.getPrePage()); System.out.println("总页数: " + pageInfo.getNextPage()); System.out.println("是否是第一页: " + pageInfo.isIsFirstPage()); System.out.println("是否是最后页: " + pageInfo.isIsLastPage());
MyBatis 的 API SqlSessionFactoryBuilder SqlSessionFactoryBuilder 是 SqlSession 工厂构建器。常用 API为
SqlSessionFactory build (InputStream inputStream)
通过加载 MyBatis 的核心文件的输入流的形式来构建一个 SqlSessionFactory
对象。
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSessionFactory SqlSessionFactory 是 SqlSession 工厂对象,它有多个方法来创建 SqlSession 实例,其中常用的有如下两个:
方法
说明
openSession()
会默认开启一个事务,但事务不会自动提交,执行更新操作需要手动提交事务。
openSession(boolean autoCommit)
参数为是否自动提交,如果设置为 true,则不需要手动提交事务。
SqlSession sqlSession = sqlSessionFactory.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 () ;
MyBatis 代理开发方式 说明
采用 MyBatis 的代理开发方式实现 DAO 层的开发,这种方式是企业开发的主流。
Mapper 接口开发方法只需要程序员编写 Mapper 接口(相当于 DAO 接口),由 MyBatis 框架根据接口定义创建接口的动态代理对象。
Mapper 接口开发需要遵循以下规范:
Mapper.xml 文件中的 namespace
与 mapper 接口的全限定名相同
Mapper 接口方法名和 Mapper.xml 中定义的每个 statement 的 id 相同
Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
示例代码
<mapper namespace ="com.vsneko.dao.UserMapper" > <delete id ="deleteUser" parameterType ="int" > delete from user where id=#{id} </delete > </mapper >
package com.vsneko.dao;public interface UserMapper { void deleteUser (int id) ; }
public static void main (String[] args) throws IOException { InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml" ); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(true ); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); userMapper.deleteUser(1 ); }
MyBatis 动态 SQL 语句 概述 在 MyBatis 的映射文件中,有时候业务逻辑比较复杂,SQL语句是动态变化的,这时候就需要用到 MyBatis 的动态 SQL 语句了。
官方描述:
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
动态SQL-if <select id ="selectById" resultType ="user" parameterType ="user" > select * from user <where > <if test ="id!=0" > and id=#{id} </if > <if test ="name!=null" > and name=#{name} </if > <if test ="age!=0" > and age=#{age} </if > </where > </select >
动态SQL-foreach <select id ="selectByIds" resultType ="user" parameterType ="list" > select * from user <where > <foreach collection ="list" open ="id in (" close =")" separator ="," item ="id" > #{id} </foreach > </where > </select >
SQL片段的抽取 这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。
<sql id ="selectUser" > select * from user</sql > <select id ="selectByIds" resultType ="user" parameterType ="list" > <include refid ="selectUser" /> <where > <foreach collection ="list" open ="id in (" close =")" separator ="," item ="id" > #{id} </foreach > </where > </select >
MyBatis 多表操作 一对一关系 public class Orders { private int id; private Date orderTime; private double total; private int uid; private User user; }
<resultMap id ="orderMap" type ="orders" > <id 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" /> <result column ="birthday" property ="user.birthday" /> </resultMap > <select id ="findAll" resultMap ="orderMap" > SELECT *,o.id oid FROM orders o, user u WHERE o.uid=u.id </select >
或者使用 association
标签:
<resultMap id ="orderMap" type ="orders" > <id column ="oid" property ="id" /> <result column ="orderTime" property ="orderTime" /> <result column ="total" property ="total" /> <association property ="user" javaType ="user" > <id column ="uid" property ="id" /> <result column ="username" property ="username" /> <result column ="password" property ="password" /> <result column ="birthday" property ="birthday" /> </association > </resultMap >
一对多关系 public class User { private int id; private String username; private String password; private Date birthday; private List<Orders> ordersList; }
<resultMap id ="userMap" type ="user" > <id column ="uid" property ="id" /> <result column ="username" property ="username" /> <result column ="password" property ="password" /> <result column ="birthday" property ="birthday" /> <collection property ="ordersList" ofType ="orders" > <id column ="oid" property ="id" /> <result column ="orderTime" property ="orderTime" /> <result column ="total" property ="total" /> </collection > </resultMap > <select id ="findAll" resultMap ="userMap" > SELECT *, o.id oid FROM user u, orders o WHERE u.id=o.uid </select >
多对多关系 多对多关系的配置与一对多的配置一样,只是SQL语句会有些区别,多对多关系有一个中间表。具体配置参考一对多的配置。
MyBatis 整合 Spring 在 spring 配置文件中配置 sessionFactory 之后就可以直接在 service 层自动注入了。
需要 mybatis-spring
的依赖。
<dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 2.0.5</version > </dependency >
<context:property-placeholder location ="classpath:jdbc.properties" /> <bean id ="dataSource" class ="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name ="driverClass" value ="${jdbc.driver}" /> <property name ="jdbcUrl" value ="${jdbc.url}" /> <property name ="user" value ="${jdbc.username}" /> <property name ="password" value ="${jdbc.password}" /> </bean > <bean id ="sessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="dataSource" /> <property name ="configLocation" value ="classpath:SqlMapConfig.xml" /> <property name ="typeAliasesPackage" value ="com.vsneko.domain" /> </bean > <bean class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="com.vsneko.mapper" /> </bean >
在 service 层中使用:
@Service("userService") public class UserService { @Autowired private UserMapper userMapper; public List<User> findAll () { return userMapper.selectAll(); } }