Mybatis的前身是iBatis,iBatis原本就是apache的一個開源項目,2010年該項目有apache遷移到了google code,并改名為MyBatis。
- 簡介
MyBatis支持普通SQL查詢,存儲過程和高級映射的優秀持久層框架,消除了幾乎所有的JDBC代碼和參數的手動設置以及結果集的檢索。MyBatis可以使用XML或者注解用于配置和映射,將接口和JavaBean映射成數據庫的記錄。
每一個MyBatis的應用程序都以一個SqlSessionFactory對象的實例為核心。SqlSessionFactory對象的實例可以通過SqlSessionFactoryBuilder對象來獲得,SqlSessionFactoryBuilder對象可以從XML配置文件或從Configuration類獲得。例如:
String resource ="com/alvinliang/mybatis/mybatis-config.xml";
InputStreamis= Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =newSqlSessionFactoryBuilder().build(is);
XML配置文件包含了對MyBatis系統的核心設置,有獲取數據庫連接實例的數據源和決定事務范圍和控制事務管理器。詳細內容后面會講,這里給出一個簡單示例:
<?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="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/alvinliang/mybatis/BlogMapper.xml"/>
</mappers>
</configuration>
還有很多可以配置,上面指出了最關鍵的部分。environment元素體里包含對事務管理和連接池的環境配置。mapppers元素包含了所有mapper映射器,這些mapper的xml文件包含了SQL代碼和映射定義信息。
詳細配置,見Mybatis官網
2.總體流程
(1) 加載配置并初始化
觸發條件:加載配置文件,將SQL配置文件加載成一個個MappedStatement對象。
關于MappedStatement是Mybatis的幾個重要類,詳細見
(2) 接受調用請求
觸發條件:調用MyBatis提供的API
傳入參數:為SQL的ID和傳入參數對象
處理過程:將請求傳遞給下層的請求的處理層進行處理。
(3) 處理操作請求
觸發條件:API接口層傳遞請求過來
傳入參數:為SQL的ID和傳入參數對象
處理過程:
(A)根據SQL的ID查找對應的MappedStatement對象。
(B)根據傳入參數對象解析MappedStatement對象,得到最終要執行的SQL和執行傳入參數。
(C)獲取數據庫連接,根據得到的最終SQL語句和執行傳入參數到數據庫執行,并得到執行結果。
(D)根據MappedStatement對象中的結果映射配置對得到的執行結果進行轉換處理,并得到最終的處理結果。
(E)釋放連接資源。
(4)返回處理結果將最終的處理結果返回。
3.功能框架
Mybatis可以分為三層:
(1) API接口層:提供給外部使用的接口API,開發人員可以通過本地API來操縱數據庫。
(2) 數據處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。
(3) 基礎支持層:負責最基本的功能支撐,包括連接管理、事務管理、配置加載和緩存處理等。
4.簡單使用
先從SqlSessionFactory對象來獲得SqlSession的實例,SqlSession對象包含了對數據庫的所有執行SQL操作的方法。例如:
SqlSession session = sqlSessionFactory.openSession();
try {
Blogblog = (Blog) session.selectOne("com.alvinliang.mybatis.BlogMapper.selectBlog", 1);
} finally {
session.close();
}
下面給出映射的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.alvinliang.mybatis.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
這里只是給出了一個簡潔的例子,其中在命名空間“com.alvinliang.mybatis.BlogMapper”中定義了一個名為“selectBlog”的映射語句,這樣它允許你使用完全限定名“com.alvinliang.mybatis.BlogMapper.selectBlog”來調用映射語句。
注意:現在命名空間是必須的,而且命名空間使得接口綁定成為可能,使用命名空間并將它放在合適的Java包空間下,將會使你的代碼變得簡潔,會在很長的時間內提高MyBatis的作用。
- 范圍和生命周期
SqlSessionFactoryBuilder
這個類可以被實例化,使用和丟棄。一旦你創建了SqlSessionFactory后,這個類就不需要了。因此SqlSessionFactoryBuilder實例的最佳范圍就是方法范圍(也就是本地方法變量)。你可以重用SqlSessionFactoryBuilder來創建多個SqlSessionFactory實例。
SqlSessionFactory
一旦被創建,SqlSessionFactory應該在你的應用執行期間都存在,沒有理來處理或者重新創建它。使用SqlSessionFactory的最佳實踐是在應用運行期間不要重復創建多次。因此SqlSessionFactory的生命周期是Application范圍。很多方法可以做到,如單例模式或者靜態單例模式。
SqlSession
每個線程都應該有它自己的SqlSession實例。SqlSession的實例不能被共享,也是線程不安全的。因此最佳范圍應該是request或者method范圍。絕對不能將SqlSession實例的引用放在一個類的靜態字段甚至是實例字段中。也絕對不能將SqlSession實例引用放在任何類型的管理范圍中,比如Servlet中的HttpSession。如果你正在使用Web框架,可以考慮將SqlSession放在一個和Http Request對象相似的范圍內。下面的示例就是一個確保SqlSession關閉的基本模式:
SqlSession session = sqlSessionFactory.openSession();
try {
// do work
} finally {
session.close();
}
在你的代碼中一貫的使用這種模式,將會保證所有的數據庫資源都正常的關閉。
完整的Example
**Step1: **Create a Maven project and configure MyBatis dependencies.
<project xmlns='http://maven.apache.org/POM/4.0.0'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xsi:schemaLocation='http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd'>
<modelVersion>4.0.0</modelVersion>
<groupId>com.sivalabs</groupId>
<artifactId>mybatis-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mybatis-demo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
Step#2: Create the table USER and a Java domain Object User as follows:
CREATE TABLE user (
user_id int(10) unsigned NOT NULL auto_increment,
email_id varchar(45) NOT NULL,
password varchar(45) NOT NULL,
first_name varchar(45) NOT NULL,
last_name varchar(45) default NULL,
PRIMARY KEY (user_id),
UNIQUE KEY Index_2_email_uniq (email_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
package com.sivalabs.mybatisdemo.domain;
public class User
{
private Integer userId;
private String emailId;
private String password;
private String firstName;
private String lastName;
@Override
public String toString() {
return 'User [userId=' + userId + ', emailId=' + emailId
+ ', password=' + password + ', firstName=' + firstName
+ ', lastName=' + lastName + ']';
}
//setters and getters
}
Step#3: Create MyBatis configuration files.
a) Create jdbc.properties file in src/main/resources folder
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis-demo
jdbc.username=root
jdbc.password=admin
b) Create mybatis-config.xml file in src/main/resources folder
<?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='jdbc.properties'/>
<typeAliases>
<typeAlias type='com.sivalabs.mybatisdemo.domain.User' alias='User'></typeAlias>
</typeAliases>
<environments default='development'>
<environment id='development'>
<transactionManager type='JDBC'/>
<dataSource type='POOLED'>
<property name='driver' value='${jdbc.driverClassName}'/>
<property name='url' value='${jdbc.url}'/>
<property name='username' value='${jdbc.username}'/>
<property name='password' value='${jdbc.password}'/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource='com/sivalabs/mybatisdemo/mappers/UserMapper.xml'/>
</mappers>
</configuration>
Step#4: Create an interface UserMapper.java in src/main/java folder in com.sivalabs.mybatisdemo.mappers package.
package com.sivalabs.mybatisdemo.mappers;
import java.util.List;
import com.sivalabs.mybatisdemo.domain.User;
public interface UserMapper
{
public void insertUser(User user);
public User getUserById(Integer userId);
public List<User> getAllUsers();
public void updateUser(User user);
public void deleteUser(Integer userId);
}
Step#5: Create UserMapper.xml file in src/main/resources folder in com.sivalabs.mybatisdemo.mappers package.
<?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.sivalabs.mybatisdemo.mappers.UserMapper'>
<select id='getUserById' parameterType='int' resultType='com.sivalabs.mybatisdemo.domain.User'>
SELECT
user_id as userId,
email_id as emailId ,
password,
first_name as firstName,
last_name as lastName
FROM USER
WHERE USER_ID = #{userId}
</select>
<!-- Instead of referencing Fully Qualified Class Names we can register Aliases in mybatis-config.xml and use Alias names. -->
<resultMap type='User' id='UserResult'>
<id property='userId' column='user_id'/>
<result property='emailId' column='email_id'/>
<result property='password' column='password'/>
<result property='firstName' column='first_name'/>
<result property='lastName' column='last_name'/>
</resultMap>
<select id='getAllUsers' resultMap='UserResult'>
SELECT * FROM USER
</select>
<insert id='insertUser' parameterType='User' useGeneratedKeys='true' keyProperty='userId'>
INSERT INTO USER(email_id, password, first_name, last_name)
VALUES(#{emailId}, #{password}, #{firstName}, #{lastName})
</insert>
<update id='updateUser' parameterType='User'>
UPDATE USER
SET
PASSWORD= #{password},
FIRST_NAME = #{firstName},
LAST_NAME = #{lastName}
WHERE USER_ID = #{userId}
</update>
<delete id='deleteUser' parameterType='int'>
DELETE FROM USER WHERE USER_ID = #{userId}
</delete>
</mapper>
Step#6: Create MyBatisUtil.java to instantiate SqlSessionFactory.
package com.sivalabs.mybatisdemo.service;
import java.io.IOException;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class MyBatisUtil
{
private static SqlSessionFactory factory;
private MyBatisUtil() {
}
static
{
Reader reader = null;
try {
reader = Resources.getResourceAsReader('mybatis-config.xml');
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
factory = new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSessionFactory getSqlSessionFactory()
{
return factory;
}
}
Step#7: Create UserService.java in src/main/java folder.
package com.sivalabs.mybatisdemo.service;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.sivalabs.mybatisdemo.domain.User;
import com.sivalabs.mybatisdemo.mappers.UserMapper;
public class UserService
{
public void insertUser(User user) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.insertUser(user);
sqlSession.commit();
}finally{
sqlSession.close();
}
}
public User getUserById(Integer userId) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
return userMapper.getUserById(userId);
}finally{
sqlSession.close();
}
}
public List<User> getAllUsers() {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
return userMapper.getAllUsers();
}finally{
sqlSession.close();
}
}
public void updateUser(User user) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.updateUser(user);
sqlSession.commit();
}finally{
sqlSession.close();
}
}
public void deleteUser(Integer userId) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
userMapper.deleteUser(userId);
sqlSession.commit();
}finally{
sqlSession.close();
}
}
}
Step#8: Create a JUnit Test class to test UserService methods.
package com.sivalabs.mybatisdemo;
import java.util.List;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.sivalabs.mybatisdemo.domain.User;
import com.sivalabs.mybatisdemo.service.UserService;
public class UserServiceTest
{
private static UserService userService;
@BeforeClass
public static void setup()
{
userService = new UserService();
}
@AfterClass
public static void teardown()
{
userService = null;
}
@Test
public void testGetUserById()
{
User user = userService.getUserById(1);
Assert.assertNotNull(user);
System.out.println(user);
}
@Test
public void testGetAllUsers()
{
List<User> users = userService.getAllUsers();
Assert.assertNotNull(users);
for (User user : users)
{
System.out.println(user);
}
}
@Test
public void testInsertUser()
{
User user = new User();
user.setEmailId('test_email_'+System.currentTimeMillis()+'@gmail.com');
user.setPassword('secret');
user.setFirstName('TestFirstName');
user.setLastName('TestLastName');
userService.insertUser(user);
Assert.assertTrue(user.getUserId() != 0);
User createdUser = userService.getUserById(user.getUserId());
Assert.assertNotNull(createdUser);
Assert.assertEquals(user.getEmailId(), createdUser.getEmailId());
Assert.assertEquals(user.getPassword(), createdUser.getPassword());
Assert.assertEquals(user.getFirstName(), createdUser.getFirstName());
Assert.assertEquals(user.getLastName(), createdUser.getLastName());
}
@Test
public void testUpdateUser()
{
long timestamp = System.currentTimeMillis();
User user = userService.getUserById(2);
user.setFirstName('TestFirstName'+timestamp);
user.setLastName('TestLastName'+timestamp);
userService.updateUser(user);
User updatedUser = userService.getUserById(2);
Assert.assertEquals(user.getFirstName(), updatedUser.getFirstName());
Assert.assertEquals(user.getLastName(), updatedUser.getLastName());
}
@Test
public void testDeleteUser()
{
User user = userService.getUserById(4);
userService.deleteUser(user.getUserId());
User deletedUser = userService.getUserById(4);
Assert.assertNull(deletedUser);
}
}
Now, I will explain how to perform CRUD operations using MyBatis Annotation support without need of Queries configuration in XML mapper files.
Step#1: Create a table BLOG and a java domain Object Blog.
CREATE TABLE blog (
blog_id int(10) unsigned NOT NULL auto_increment,
blog_name varchar(45) NOT NULL,
created_on datetime NOT NULL,
PRIMARY KEY (blog_id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
package com.sivalabs.mybatisdemo.domain;
import java.util.Date;
public class Blog {
private Integer blogId;
private String blogName;
private Date createdOn;
@Override
public String toString() {
return 'Blog [blogId=' + blogId + ', blogName=' + blogName
+ ', createdOn=' + createdOn + ']';
}
//Seeters and getters
}
Step#2: Create UserMapper.java interface with SQL queries in Annotations.
package com.sivalabs.mybatisdemo.mappers;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.sivalabs.mybatisdemo.domain.Blog;
public interface BlogMapper
{
@Insert('INSERT INTO BLOG(BLOG_NAME, CREATED_ON) VALUES(#{blogName}, #{createdOn})')
@Options(useGeneratedKeys=true, keyProperty='blogId')
public void insertBlog(Blog blog);
@Select('SELECT BLOG_ID AS blogId, BLOG_NAME as blogName, CREATED_ON as createdOn FROM BLOG WHERE BLOG_ID=#{blogId}')
public Blog getBlogById(Integer blogId);
@Select('SELECT * FROM BLOG ')
@Results({
@Result(id=true, property='blogId', column='BLOG_ID'),
@Result(property='blogName', column='BLOG_NAME'),
@Result(property='createdOn', column='CREATED_ON')
})
public List<Blog> getAllBlogs();
@Update('UPDATE BLOG SET BLOG_NAME=#{blogName}, CREATED_ON=#{createdOn} WHERE BLOG_ID=#{blogId}')
public void updateBlog(Blog blog);
@Delete('DELETE FROM BLOG WHERE BLOG_ID=#{blogId}')
public void deleteBlog(Integer blogId);
}
Step#3: Configure BlogMapper in mybatis-config.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>
<properties resource='jdbc.properties'/>
<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-demo'/>
<property name='username' value='root'/>
<property name='password' value='admin'/> -->
<property name='driver' value='${jdbc.driverClassName}'/>
<property name='url' value='${jdbc.url}'/>
<property name='username' value='${jdbc.username}'/>
<property name='password' value='${jdbc.password}'/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class='com.sivalabs.mybatisdemo.mappers.BlogMapper'/>
</mappers>
</configuration>
Step#4: Create BlogService.java
package com.sivalabs.mybatisdemo.service;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import com.sivalabs.mybatisdemo.domain.Blog;
import com.sivalabs.mybatisdemo.mappers.BlogMapper;
public class BlogService
{
public void insertBlog(Blog blog) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
blogMapper.insertBlog(blog);
sqlSession.commit();
}finally{
sqlSession.close();
}
}
public Blog getBlogById(Integer blogId) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
return blogMapper.getBlogById(blogId);
}finally{
sqlSession.close();
}
}
public List<Blog> getAllBlogs() {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
return blogMapper.getAllBlogs();
}finally{
sqlSession.close();
}
}
public void updateBlog(Blog blog) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
blogMapper.updateBlog(blog);
sqlSession.commit();
}finally{
sqlSession.close();
}
}
public void deleteBlog(Integer blogId) {
SqlSession sqlSession = MyBatisUtil.getSqlSessionFactory().openSession();
try{
BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
blogMapper.deleteBlog(blogId);
sqlSession.commit();
}finally{
sqlSession.close();
}
}
}
Step#5: Create JUnit Test for BlogService methods
package com.sivalabs.mybatisdemo;
import java.util.Date;
import java.util.List;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import com.sivalabs.mybatisdemo.domain.Blog;
import com.sivalabs.mybatisdemo.service.BlogService;
public class BlogServiceTest
{
private static BlogService blogService;
@BeforeClass
public static void setup()
{
blogService = new BlogService();
}
@AfterClass
public static void teardown()
{
blogService = null;
}
@Test
public void testGetBlogById()
{
Blog blog = blogService.getBlogById(1);
Assert.assertNotNull(blog);
System.out.println(blog);
}
@Test
public void testGetAllBlogs()
{
List<Blog> blogs = blogService.getAllBlogs();
Assert.assertNotNull(blogs);
for (Blog blog : blogs)
{
System.out.println(blog);
}
}
@Test
public void testInsertBlog()
{
Blog blog = new Blog();
blog.setBlogName('test_blog_'+System.currentTimeMillis());
blog.setCreatedOn(new Date());
blogService.insertBlog(blog);
Assert.assertTrue(blog.getBlogId() != 0);
Blog createdBlog = blogService.getBlogById(blog.getBlogId());
Assert.assertNotNull(createdBlog);
Assert.assertEquals(blog.getBlogName(), createdBlog.getBlogName());
}
@Test
public void testUpdateBlog()
{
long timestamp = System.currentTimeMillis();
Blog blog = blogService.getBlogById(2);
blog.setBlogName('TestBlogName'+timestamp);
blogService.updateBlog(blog);
Blog updatedBlog = blogService.getBlogById(2);
Assert.assertEquals(blog.getBlogName(), updatedBlog.getBlogName());
}
@Test
public void testDeleteBlog()
{
Blog blog = blogService.getBlogById(4);
blogService.deleteBlog(blog.getBlogId());
Blog deletedBlog = blogService.getBlogById(4);
Assert.assertNull(deletedBlog);
}
}