MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。MyBatis是一个优秀的持久层框架,它对JDBC的操作数据库的过程进行了封装,使开发者只需要关注SQL本身,而不需要花费精力去处理例如注册驱动、创建Connection、创建Statement、手动设置参数、结果集检索等JDBC繁杂的过程代码了。
MyBatis通过xml或注解的方式将要执行的各种Statement(Statement、PreparedStatemnt、CallableStatement)配置起来,并通过Java对象和Statement中的SQL进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL语句并将结果映射成Java对象并返回。
知道了MyBatis框架是个什么东东之后,我们还需要从全局或者整体上对MyBatis框架有一个比较清晰的认识,MyBatis框架的架构如下图所示。
对于以上架构图,我们该怎样去理解呢?可以从以下几点去理解。
MyBatis框架的代码现在是由Github来管理的,地址是https://github.com/mybatis/mybatis-3/releases。大家可从该地址下载最新版本的MyBatis框架。温馨提示:本系列教程使用的是mybatis-3.2.7这个版本的MyBatis框架,该版本的MyBatis框架的目录结构如下图所示。
首先,我们肯定是要明确开发需求的,即使用MyBatis框架能干啥?我们可以使用MyBatis框架来实现以下功能:
开发需求明确之后,我们就要搭建开发环境了。开发环境搭建的详细步骤如下:
# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
在这里,我们必须明确一点的就是,MyBatis框架默认使用log4j来输出日志信息。
<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?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="liayun" />
dataSource>
environment>
<environment id="test">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="liayun" />
dataSource>
environment>
environments>
configuration>
SqlMapConfig.xml是MyBatis框架的核心配置文件,以上文件的配置内容为数据源、事务管理。温馨提示:等后面MyBatis和Spring两个框架整合之后,environments的配置将被废除。public class User {
private int id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", birthday=" + birthday + ", address="
+ address + "]";
}
}
<mapper namespace="user">
mapper>
namespace:即命名空间,它主要用于隔离SQL语句(即不同SQL映射文件中的两个相同id的SQL语句如何来区分),这只是当前的作用,后续会讲另一层非常重要的作用。
<mappers>
<mapper resource="mybatis/user.xml" />
mappers>
正如下图所示的那样:首先,在user.xml映射文件中添加如下配置:
针对以上标签的配置,我觉得有必要说明如下几点:
还有一点需要说明,SQL语句中的#{id2}
表示使用PreparedStatement设置占位符号并将输入变量id2传到SQL语句中。说得直白点,#{}
就是一个占位符,相当于JDBC中的?
,如果入参为普通数据类型,例如Integer、String等,那么{}
内部就可以随便写了!
然后,在src目录下新建一个名为com.meimeixia.mybatis.test的包,并在该包下创建一个名为MybatisTest的单元测试类,紧接着在该类中编写一个如下的单元测试方法。
package com.meimeixia.mybatis.test;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import com.meimeixia.mybatis.pojo.User;
public class MybatisTest {
@Test
public void testGetUserById() throws IOException {
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
//1. 加载核心配置文件(创建核心配置文件的输入流对象)
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 创建SqlSessionFactory对象(通过核心配置文件的输入流对象创建SqlSessionFactory对象)
SqlSessionFactory sqlSessionFactory = ssfb.build(inputStream);
//3. 创建SqlSession对象,调用其API,得到一个结果集
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行查询,参数一:SQL语句的ID(规范一点的话,最好带上命名空间),参数二:入参
User user = sqlSession.selectOne("user.getUserById", 1);
//输出结果
System.out.println(user);
//释放资源
sqlSession.close();
}
}
以上完成的需求就是根据用户id来查询用户信息。运行以上单元测试方法,你可以看到Eclipse控制台打印出了如下内容。
一般来讲,工厂对象一般在实际开发是单例的,并不需要频繁地创建,所以我们可以抽取出一个工具类,专门用于创建SqlSessionFactory对象。我们可以在src目录下新建一个名为com.meimeixia.mybatis.utils的包,并在该包下创建一个名为SqlSessionFactoryUtils的工具类。
package com.meimeixia.mybatis.utils;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
/**
* SqlSessionFactory工具类
* @author liayun
*
*/
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
//创建核心配置文件的输入流
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//通过核心配置文件的输入流创建SqlSessionFactory对象
sqlSessionFactory = ssfb.build(inputStream);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 获取SqlSessionFactory对象
* @return
*/
public static SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
}
如此一来,MybatisTest单元测试类中的testGetUserById方法便可以写成下面这样了。
@Test
public void testGetUserById() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行查询,参数一:SQL语句的ID(规范一点的话,最好带上命名空间),参数二:入参
User user = sqlSession.selectOne("user.getUserById", 1);
//输出结果
System.out.println(user);
//释放资源
sqlSession.close();
}
现在我们就要来完成第二个需求,即根据用户名称模糊查询用户信息列表了。首先,在user.xml映射文件中添加如下配置。
这里需要说明的一点是,如果查询结果返回的是List集合,那么resultType属性的值只需要设置为List集合中的一个元素的数据类型即可。
然后,在MybatisTest单元测试类编写一个如下的单元测试方法。
@Test
public void testGetByUserName() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> list = sqlSession.selectList("user.getUserByUserName", "%张%");
for (User user : list) {
System.out.println(user);
}
//释放资源
sqlSession.close();
}
运行以上单元测试方法,你便可以看到Eclipse控制台打印出了如下内容。
在实际开发中建议使用#{}
占位符这种方式,因为这样可以防止SQL注入。除了以上这种方式外,在此我还介绍第二种方式,即在user.xml映射文件中添加如下配置:
${value}
表示使用参数将${value}
替换,做字符串的拼接,${}
为字符串拼接指令。温馨提示:如果是取简单数据类型的参数,括号中的值必须为value。
如此一来,MybatisTest单元测试类中的testGetByUserName方法就要修改成下面这个样子了。
@Test
public void testGetByUserName() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
List<User> list = sqlSession.selectList("user.getUserByUserName", "张");
for (User user : list) {
System.out.println(user);
}
//释放资源
sqlSession.close();
}
此时,运行以上单元测试方法,你便可以看到Eclipse控制台打印出了如下内容。
很明显这种方式在实际开发中是不建议使用的,因为无法防止SQL注入。
#{}
表示一个占位符号,可以很好地去避免SQL注入。其原理是将占位符位置的整个参数和SQL语句两部分提交给数据库,数据库去执行SQL语句,去表中匹配所有的记录是否和整个参数一致。
${}
表示一个SQL拼接符号,其原理是在向数据库发出SQL语句之前去拼接好SQL语句再提交给数据库执行。
一般情况下建议使用#{}
,只有在特殊情况下才必须要用到${}
,比如:
${}
将排序字段传入SQL中;${}
将表名传入SQL中。现在我们就要来完成第三个需求,即添加用户了。首先,在user.xml映射文件中添加如下配置。
如果输入参数是pojo,那么#{}
中要写的东东就是pojo类中的属性了(用到了对象图导航的思想),此时就不能随便写了。
然后,试着在MybatisTest单元测试类中编写一个如下的单元测试方法。
@Test
public void testInsertUser() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("鲁达");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("广州黄埔刘村");
sqlSession.insert("user.insertUser", user);
//释放资源
sqlSession.close();
}
运行以上单元测试方法,你便可以看到Eclipse控制台打印出了如下内容。
虽然发出了SQL语句,但是事务并没将其提交,而是回滚了。所以,User对象是无法插入到数据库user表中的。此时,我们应该将testInsertUser单元测试方法修改成下面这个样子,才能真正将User对象插入到数据库user表中。
@Test
public void testInsertUser() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("鲁达");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("广州黄埔刘村");
sqlSession.insert("user.insertUser", user);
//手动提交事务(在Mybatis中,事务是默认不提交的)
sqlSession.commit();
//释放资源
sqlSession.close();
}
再次运行以上单元测试方法,你便可以看到Eclipse控制台打印出了如下内容。
事务已提交,可发现数据库user表中插入了一条记录。不知你有没有想过SqlSession对象的insert方法的返回值代表的是什么?想必不用我说,你就应该清楚insert方法返回的是数据库影响的行数。你可以尝试着将testInsertUser单元测试方法修改成下面这个样子。
@Test
public void testInsertUser() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("鲁达222");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("广州黄埔刘村");
int insert = sqlSession.insert("user.insertUser", user);
System.out.println(insert);//返回的是数据库影响的行数
//手动提交事务(在Mybatis中,事务是默认不提交的)
sqlSession.commit();
//释放资源
sqlSession.close();
}
此时,运行以上单元测试方法,你便可以看到Eclipse控制台打印出了如下内容。
足以说明insert方法返回的是数据库影响的行数了。
现在有这样一个需求:在成功向数据库user表中插入一条记录后,想要得到MySQL数据库给我们生成的主键id,即获取主键。如何实现这个需求呢?这里就要用到MySQL数据库中的一个内置函数(即LAST_INSERT_ID())了,它返回的是auto_increment自增列新记录ID值,即它能查询出最后一条记录插入的ID值。该函数是在当前事务下取到你最后插入到表中的那条记录的ID值,而我们应知道查询操作是不需要开启事务的,增删改操作是需要开启事务的。温馨提示:该函数的调用是和会话有关的,也就是说在每一个连接里面,如果有这个函数,那么这个函数便是线程安全的!
如此一来,我们就需要将user.xml映射文件中的
标签的配置修改成下面这个样子(即添加selectKey实现将主键返回):
针对以上
标签的配置,我觉得有必要说明如下几点:
为了获取MySQL自增主键,我们还需要将testInsertUser单元测试方法修改成下面这个样子。
@Test
public void testInsertUser() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("鲁达333");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("广州黄埔刘村");
sqlSession.insert("user.insertUser", user);
System.out.println(user);
//手动提交事务(在Mybatis中,事务是默认不提交的)
sqlSession.commit();
//释放资源
sqlSession.close();
}
运行以上单元测试方法,你便可以看到Eclipse控制台打印出了如下内容。
其实,user.xml映射文件中的
标签可以简写成下面这个样子,一样可以获取到MySQL自增主键。
在这一小节中,为了方便演示,需要提前做两件事情,第一件事情是在数据库user表中加上一个uuid2字段。
第二件事情是在User类上加上相应属性,即uuid2属性。
package com.meimeixia.mybatis.pojo;
import java.util.Date;
public class User {
private Integer id;
private String username;// 用户姓名
private String sex;// 性别
private Date birthday;// 生日
private String address;// 地址
private String uuid2;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getUuid2() {
return uuid2;
}
public void setUuid2(String uuid2) {
this.uuid2 = uuid2;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", birthday=" + birthday + ", address="
+ address + ", uuid2=" + uuid2 + "]";
}
}
在使用UUID算法生成主键时,需要增加通过SELECT UUID()
语句得到一个UUID值作为主键。这时,我们可以在user.xml映射文件中添加如下配置。
因为是使用UUID算法生成主键,所以应该先生成主键然后再向数据库中插入数据,此时order属性的值应是BEFORE。
为了获取到UUID算法生成的主键,我们还需要在MybatisTest单元测试类中编写一个如下的单元测试方法。
@Test
public void testInsertUserUUID() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setUsername("武松");
user.setSex("1");
user.setBirthday(new Date());
user.setAddress("广州黄埔刘村");
sqlSession.insert("user.insertUserUUID", user);
System.out.println(user);
//手动提交事务(在Mybatis中,事务是默认不提交的)
sqlSession.commit();
//释放资源
sqlSession.close();
}
试着运行以上单元测试方法,你便可以看到Eclipse控制台打印出了如下内容。
不知你有没有想过一个问题,如果要是将以上
标签修改成下面这个样子,那么会出现什么现象呢?如果在MyBatis中配置了一个selectKey的话,那么
标签上的useGeneratedKeys="true" keyProperty="id"
配置默认就不生效了!!!
现在我们就要来完成第四个需求,即删除用户了。首先,在user.xml映射文件中添加如下配置:
然后,在MybatisTest单元测试类编写一个如下的单元测试方法。
@Test
public void testDeleteUser() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.delete("user.deleteUser", 38);
//手动提交事务(在Mybatis中,事务是默认不提交的)
sqlSession.commit();
//释放资源
sqlSession.close();
}
运行以上单元测试方法,你便可以看到Eclipse控制台打印出了如下内容。
现在我们就要来完成第五个需求,即更新用户了。首先,在user.xml映射文件中添加如下配置:
然后,在MybatisTest单元测试类编写一个如下的单元测试方法。
@Test
public void testUpdateUser() {
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtils.getSqlSessionFactory();
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setId(39);
user.setUsername("张无忌");
sqlSession.update("user.updateUser", user);
//手动提交事务(在Mybatis中,事务是默认不提交的)
sqlSession.commit();
//释放资源
sqlSession.close();
}
运行以上单元测试方法,你便可以看到Eclipse控制台打印出了如下内容。
其实,调用SqlSession对象的insert方法同样也可以达到更新用户的效果。
为什么不这样做呢?其实理论上是应该要调用update方法的,即使调用insert方法不影响最后的结果,但是我们要习惯性调用相应的方法,这样代码也会好看一点!
还记得《MyBatis快速入门第一讲——使用传统的JDBC编程所带来的问题》一文中使用JDBC编程所带来的问题吗?现在使用MyBatis这个框架就可以解决JDBC编程所带来的这些问题了。
MyBatis和Hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写SQL语句,不过MyBatis可以通过XML或注解方式灵活配置要运行的SQL语句,并将Java对象和SQL语句映射生成最终执行的SQL,最后将SQL执行的结果再映射生成Java对象。
MyBatis学习门槛低,简单易学,程序员直接编写原生态SQL,可严格控制SQL执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是MyBatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套SQL映射文件,工作量大。
Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件),如果用Hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好的。
我个人总结的MyBatis与Hibernate这两个框架的不同之处:
企业在技术选型上应考虑各个技术框架的特点去进行选型,企业要根据人力、物力等资源去衡量,以节省成本、利润最大化为目标进行选型。