MyBatis 是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和Java的POJO(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
Mybatis 通过 xml 或注解的方式将要执行的各种 statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过 java 对象和 statement 中的 sql 进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射成 PO(persisent object 持久对象)并返回。
MyBatis 中文官网网址是 http://www.mybatis.org/mybatis-3/zh/index.html,官网提供了大量的学习资源。
既然 MyBatis 是持久层框架,而传统的持久层是由 JDBC 实现的,那么我们就来分析传统 JDBC 存在哪些问题。
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbname",
"root","root");
//定义 sql 语句
String sql = "select * from userInfo where username = ?";
//获取预处理 statement
preparedStatement = connection.prepareStatement(sql);
//为占位符赋值
preparedStatement.setString(1, "林冲");
//向数据库发出 sql 语句,获取查询结果集
rs =preparedStatement.executeQuery();
//遍历查询结果集
while(rs.next()){
System.out.println(rs.getString("id");
System.out.println(rs.getString("username");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//释放资源
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
上面的代码展示了传统的 JDBC 编程,传统的 JDBC 编程步骤如下:
1、 加载数据库驱动
2、 创建并获取数据库链接
3、 创建 statement 对象
4、 拼写 sql 语句
5、 设置 sql 语句中的占位符的值
6、 执行 sql 语句并获取结果
7、 对 sql 执行结果进行解析处理
8、 释放资源
1、 数据库连接的创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连
接池可解决该问题。
2、 SQL 语句编写在 Java 代码中,这种硬编码造成代码不易维护,当 SQL 变动时需要修改 java
源代码。
3、 使用 preparedStatement 向占位符传参数存在硬编码,因为 SQL 语句的 where 条件中占
位符的个数可能会变化,修改 SQL 还要修改 Java 源代码,系统不易维护。
4、 对结果集解析存在硬编码,SQL 语句变化导致解析代码变化,系统不易维护。
MyBatis 是优秀的持久层框架,它能够解决 JDBC 编程中存在的问题。接下来先了解MyBatis 的架构。
上图是 MyBatis 的整体架构,从上之下依次的作用如下:
MyBatis 的 jar 包可以从 https://github.com/mybatis/mybatis-3/releases 下载,下载界面如下图所示:
若使用 maven 构建,pom.xml 的依赖如下:
org.mybatis
mybatis
3.4.5
创建 MyBatis 的配置文件 SqlMapConfig.xml 和映射配置文件 Mapper.xml,并将
Mapper.xml 加载到 SqlMapConfig.xml 中。
开发一个在线订购商品的电子商务网站,包括用户管理、商品管理、订单管理。后续课程都基于该需求进行讲解。本章完成用户管理模块,具体业务包括
创建数据库命名为 eshop,创建用户表命名为 userinfo,创建商品表命名为 goods,创建订单表命名为 orders,创建订单明细表命名为 orderdetail。
CREATE DATABASE eshop;
USE eshop;
#创建用户表
CREATE TABLE userinfo (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
userName VARCHAR(20) NOT NULL , #用户名称
userPass VARCHAR(48) NOT NULL, #用户密码
birthday DATE DEFAULT NULL , #生日
gender CHAR(1) DEFAULT NULL , #性别
address VARCHAR(256) DEFAULT NULL #地址
);
#商品表
CREATE TABLE goods (
id INT AUTO_INCREMENT PRIMARY KEY,
goodsname VARCHAR(32), #商品名称
price FLOAT(10,1), #商品定价
memo TEXT , #商品描述
pic VARCHAR(200) , #商品图片
createtime DATETIME #生产日期
);
#订单表
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,#订单号
userid INT , #下单用户 id
createtime DATETIME NOT NULL, #创建订单时间
memo VARCHAR(100) #备注
);
#外键:订单表 userid 引用 userinfo 的 id
ALTER TABLE orders ADD CONSTRAINT fk_orders_userinfo_id FOREIGN KEY (userid)
REFERENCES userinfo (id);
#订单明细表
CREATE TABLE orderdetail (
id INT AUTO_INCREMENT PRIMARY KEY,
orderid INT NOT NULL , #订单 id
goodsid INT NOT NULL , #商品 id
itemsnum INT DEFAULT NULL #商品购买数量
);
#外键:orderdetail 的 ordersid 引用表 orders 表的 id
ALTER TABLE orderdetail ADD CONSTRAINT fk_orderdetail_orders_id FOREIGN KEY (orderid)
REFERENCES orders (id);
#外键:orderdetail 的 goodsid 引用表 goods 表的 id
ALTER TABLE orderdetail ADD CONSTRAINT fk_orderdetail_goods_id FOREIGN KEY (goodsid)
REFERENCES goods (id);
#初始化用户数据
INSERT INTO userinfo(userName,userPass,birthday,gender,address)
VALUES('admin','admin','1980-10-10','男','陕西西安');
INSERT INTO userinfo(userName,userPass,birthday,gender,address)
VALUES('林冲','lichong','1982-11-10','男','河南开封');
INSERT INTO userinfo(userName,userPass,birthday,gender,address)
VALUES('扈三娘','husanniang','1981-03-10','女','山东聊城');
INSERT INTO userinfo(userName,userPass,birthday,gender,address)
VALUES('孙二娘','sunerniang','1979-03-10','女','山东曾头市');
#初始化商品数据
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('平谷大桃','33.6','产自河北','pinggudatao.jpg','2017-12-12 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('油桃','17.6','产自河北','youtao.jpg','2017-12-13 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('水蜜桃','39.6','产自河北','shuimitao.jpg','2017-12-14 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('蟠桃','33.6','产自河北','pantao.jpg','2017-12-11 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('毛桃','31.6','产自河北','maotao.jpg','2017-10-12 12:12:12');
INSERT INTO goods(goodsname,price,memo,pic,createtime)
VALUES('樱桃','43.6','产自河北','yingtao.jpg','2017-11-22 12:12:12');
#初始化一条订单
INSERT INTO orders(userid,createtime,memo)
VALUES(2,'2017-09-21 16:26:51','要新鲜的');
#初始化订单明细
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(1,1,3);
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(1,2,2);
#初始化一条订单
INSERT INTO orders(userid,createtime,memo)
VALUES(2,'2017-09-22 16:26:50','和上次的一样');
#初始化订单明细
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(2,1,3);
INSERT INTO orderdetail(orderid,goodsid,itemsnum)VALUES(2,2,2);
第一步:创建 maven 项目
在 IDEA中创建 maven-archetype-quickstart 项目,groupId 为 cn.itlaobing,artifactId 为mybatis,version 为 0.0.1-SNAPSHOT。
第二步:设置 pom.xml 文件
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.10version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.5version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.43version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
第三步:创建 MyBatis 配置文件
在项目中创建 Source Folder,命名为 src/main/resources,在该目录下创建 MyBatis 配置文件,命名为 SqlMapConfig.xml(该配置文件的文件名可自定义,建议使用 SqlmapConfig.xml),具体配置如下:
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/eshop?characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="root" />
dataSource>
environment>
environments>
<mappers>
<mapper resource="userInfo.xml" />
mappers>
configuration>
第四步:创建映射文件
在 src/main/resources 目录下创建映射文件,命名为 userinfo.xml,具体配置如下:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="eshop">
mapper>
第五步:创建日志文件
在 src/main/resources 目录下创建日志文件 log4j.properties,将日志级别设置为 debug,具体配置如下:
log4j.rootLogger=debug,stdout,file
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.MaxFileSize=1MB
log4j.appender.file.MaxBackupIndex=1024
log4j.appender.file.File=c\:/eshop.html
log4j.appender.file.layout=org.apache.log4j.HTMLLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss}
[%c]-[%-5p] %m%n%n
第一步:创建 POJO 类
POJO(Plain Ordinary Java Object, 普通 JAVA 对象,只有属性及其 set/get 方法),也就是实体类。在 src/main/java 目录中创建包 com.xawl.mybatis.model,在该包下创建 POJO 类UserInfoModel,代码如下:
public class UserInfoModel implements Serializable{
private int id;
private String userName;
private String userPass;
private Date birthday;
private String gender;
private String address;
@Override
public String toString() {
return this.id+"--"+this.userName +"--"+this.userPass+"--"+this.birthday+
"--"+this.gender+"--"+this.address;
}
省略 get/set 方法
}
第二步:配置 SQL 映射
在 userInfo.xml 映射配置文件中创建 Mapped Statement ID,实现根据用户 id 查询用户
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="eshop">
<select id="findUserInfoById" parameterType="int"
resultType="cn.itlaobing.mybatis.model.UserInfoModel">
SELECT * FROM userInfo WHERE id=#{id}
select>
mapper>
第三步:创建单元测试类
在 src/test/java 目录中创建包 com.xawl.mybatis.test,在该包下创建单元测试类
TestEshop,代码如下:
package cn.itlaobing.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.Before;
import org.junit.Test;
import cn.itlaobing.mybatis.model.UserInfoModel;
public class TestEshop {
// 定义会话工厂对象 sqlSessionFactory
private SqlSessionFactory sqlSessionFactory = null;
@Before
public void setUp() throws IOException {
// 加载 MyBatis 配置文件
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建会话工厂,传入 mybatis 的配置文件信息
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testFindUserInfoById() {
// 通过会话工厂得到 SqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
/*
* 通过 SqlSession 操作数据库,sqlSession.selectOne 用于从库中查出一条记录
* 第一个参数:Mapped Statement ID,其值为 namespace+"."+id
* 第二个参数:指定和映射文件中所匹配的 parameterType 类型的参数
* 返回值:sqlSession.selectOne 的返回值与映射文件中 resultType 类型的值一致
*/
UserInfoModel model = sqlSession.selectOne("eshop.findUserInfoById", 1);
// 关闭 sqlSession 对象
sqlSession.close();
System.out.println(model.getId() + "-" + model.getUserName());
}
}
第四步:单元测试
运行单元测试,控制台输出日志如下
log4j:WARN No such property [conversionPattern] in org.apache.log4j.HTMLLayout.
12:20:20,852 DEBUG LogFactory:135 - Logging initialized using 'class
org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
12:20:20,977 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
12:20:20,977 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
12:20:20,977 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
12:20:20,977 DEBUG PooledDataSource:335 - PooledDataSource forcefully closed/removed all connections.
12:20:21,055 DEBUG JdbcTransaction:137 - Opening JDBC Connection
12:20:21,289 DEBUG PooledDataSource:406 - Created connection 811587677.
12:20:21,305 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection
[com.mysql.jdbc.JDBC4Connection@305fd85d]
12:20:21,305 DEBUG findUserInfoById:159 - ==> Preparing: SELECT * FROM userInfo WHERE id=?
12:20:21,336 DEBUG findUserInfoById:159 - ==> Parameters: 1(Integer)
12:20:21,352 DEBUG findUserInfoById:159 - <== Total: 1
12:20:21,352 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection
[com.mysql.jdbc.JDBC4Connection@305fd85d]
12:20:21,367 DEBUG JdbcTransaction:91 - Closing JDBC Connection
[com.mysql.jdbc.JDBC4Connection@305fd85d]
12:20:21,367 DEBUG PooledDataSource:363 - Returned connection 811587677 to pool.
1--admin--admin--1980-10-10--男--陕西西安
第一步:创建 Mapped Statement ID
在映射文件 userinfo.xml 中添加 Mapped Statement ID,代码如下:
<mapper namespace="eshop">
<select id="findUserInfoByName" parameterType="java.lang.String"
resultType="cn.itlaobing.mybatis.model.UserInfoModel">
SELECT * FROM userInfo WHERE userName LIKE '%${value}%'
select>
mapper>
第二步:编写单元测试
在 TestEshop 单元测试类中添加单元测试方法,代码如下:
@Test
public void testFindUserInfoByName() {
SqlSession sqlSession = sqlSessionFactory.openSession();
List<UserInfoModel> list = sqlSession.selectList("eshop.findUserInfoByName","娘");
sqlSession.close();
System.out.println(list);
}
第三步:单元测试
运行单元测试,控制台输出日志如下
14:24:21,341 DEBUG findUserInfoByName:159 - ==> Preparing: SELECT * FROM userInfo WHERE
userName LIKE '%娘%'
14:24:21,372 DEBUG findUserInfoByName:159 - ==> Parameters:
14:24:21,419 DEBUG findUserInfoByName:159 - <== Total: 2
14:24:21,419 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection
[com.mysql.jdbc.JDBC4Connection@26a7b76d]
14:24:21,419 DEBUG JdbcTransaction:91 - Closing JDBC Connection
[com.mysql.jdbc.JDBC4Connection@26a7b76d]
14:24:21,419 DEBUG PooledDataSource:363 - Returned connection 648525677 to pool.
[3--扈三娘--husanniang--1981-03-10--女--山东聊城, 4--孙二娘--sunerniang--1979-03-10--女--山东曾头
市]