Java數(shù)據(jù)持久化之mybatis
一. mybatis簡(jiǎn)介
mybatis是一個(gè)簡(jiǎn)化和實(shí)現(xiàn)了Java數(shù)據(jù)持久層的開源框架,它抽象了大量的JDBC冗余代碼,并且提供簡(jiǎn)單易用的api和數(shù)據(jù)庫(kù)交互.mybatis的前身是ibatis,ibatis由Clinton Begin 創(chuàng)建.mybatis3是ibatis的全新設(shè)計(jì),支持注解和mapper.
MyBatis 流行的主要原因在于它的簡(jiǎn)單性和易使用性。在 Java 應(yīng)用程序中,數(shù)據(jù)持久化層涉及到的工作有:將從數(shù) 據(jù)庫(kù)查詢到的數(shù)據(jù)生成所需要的 Java 對(duì)象;將 Java 對(duì)象中的數(shù)據(jù)通過 SQL 持久化到數(shù)據(jù)庫(kù)中。
MyBatis 通過抽象底層的JDBC代碼,自動(dòng)化SQL結(jié)果集產(chǎn)生Java對(duì)象、Java對(duì)象的數(shù)據(jù)持久化數(shù)據(jù)庫(kù)中的過程 使得對(duì) SQL 的使用變得容易。
1.1 原始的JDBC操作: Java 通過 Java 數(shù)據(jù)庫(kù)連接(Java DataBase Connectivity,JDBC)API 來操作關(guān)系型數(shù)據(jù)庫(kù),但是 JDBC 是一個(gè)
非常底層的 API,我們需要書寫大量的代碼來完成對(duì)數(shù)據(jù)庫(kù)的操作。
讓我們演示一下我們是怎樣使用純的 JDBC 來對(duì)表 STUDENTS 實(shí)現(xiàn)簡(jiǎn)單的 select 和 insert 操作的。
假設(shè)表 STUDENTS 有 STUD_ID,NAME,EMAIL 和 DOB 字段。對(duì)應(yīng)的 Student JavaBean 定義如下:
public class Student {
private Integer studId;
private String name;
private String email;
private Date dob;
//setter and getter
}
下面的StudentService實(shí)現(xiàn)了通過JDBC對(duì)表students的操作
public Student findStudentById(Integer studId) {
Student student = null;
Connection connection = null;
try {
connection = getDataBaseConnection();
String sql = "select * from students where stud_id=?";
PreparedStatement stamte = connection.prepareStatement(sql);
stamte.setInt(1, studId);
ResultSet rSet = stamte.executeQuery();
if (rSet.next()) {
student = new Student();
student.setStudId(rSet.getInt("stud_id"));
student.setName(rSet.getString("name"));
student.setEmail("email");
student.setDob(rSet.getDate("dob"));
}
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}finally {
if (connection != null) {
try {
connection.close();
} catch (Exception e2) {
// TODO: handle exception
throw new RuntimeException(e2);
}
}
}
return student;
}
public void createStudent(Student student) {
Connection connection = null;
try {
String sql = "insert into students(stud_id,name,email,dob) "
+ "values(?,?,?,?)";
connection = getDataBaseConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, student.getStudId());
preparedStatement.setString(2, student.getName());
preparedStatement.setString(3, student.getEmail());
preparedStatement.setDate(4, new java.sql.Date(student.getDob().getTime()));
preparedStatement.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException(e);
}finally {
if (connection!=null) {
try {
connection.close();
} catch (Exception e2) {
// TODO: handle exception
throw new RuntimeException(e2);
}
}
}
}
protected Connection getDataBaseConnection() throws SQLException {
try {
Class.forName("com.mysql.jdbc.Driver");
return DriverManager.getConnection("jdbc:mysql://localhost:3306"
+ "/mybatis", "root", "1l9o9v0e");
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException(e);
}
}
上面的每一步操作都有大量的重復(fù)代碼:創(chuàng)建一個(gè)連接,創(chuàng)建一個(gè)statement對(duì)象,設(shè)置參數(shù),關(guān)閉資源. mybatis抽象了上述的這些相同的任務(wù):如準(zhǔn)備需要被執(zhí)行的SQL statement對(duì)象并且將Java對(duì)象作為輸入數(shù)據(jù) 傳遞給 statement 對(duì)象的任務(wù),進(jìn)而開發(fā)人員可以專注于真正重要的方面。
另外,MyBatis 自動(dòng)化了將從輸入的 Java 對(duì)象中的屬性設(shè)置成查詢參數(shù)、從 SQL 結(jié)果集上生成 Java 對(duì)象這兩個(gè)過 程。
現(xiàn)在讓我們看看怎樣通過 MyBatis 實(shí)現(xiàn)上述的方法:
- 在 SQL Mapper映射配置文件中配置SQL語(yǔ)句,假定文件為StudentMapper.xml
<select id="findStudentById" parameterType="int" resultType="Student">
SELECT STUD_ID AS STUDID,NAME,EMAIL,DOB
FROM STUDENTS WHERE STUD_ID=#{Id}
</select>
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB)
VALUES(#{studId},#{name},#{email},#{dob}))
</insert>
- 創(chuàng)建一個(gè)StudentMapper接口
public interface StudentMapper {
Student findStudentById(Integer id);
void insertStudent(Student student);
}
- 在Java代碼操作中,可以使用以下代碼方式觸發(fā)SQL語(yǔ)句
SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.findStudentById(studId);
} finally {
// TODO: handle finally clause
sqlSession.close();
}
//或者
SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
studentMapper.insertStudent(student);
sqlSession.commit();
} finally {
// TODO: handle finally clause
sqlSession.close();
}
看起來和原生的JDBC操作簡(jiǎn)單了許多:不需要?jiǎng)?chuàng)建Connection和PreparedStatement,不需要自己對(duì)每一次數(shù)據(jù)庫(kù)操作進(jìn)行手動(dòng)設(shè) 置參數(shù)和關(guān)閉連接。只需要配置數(shù)據(jù)庫(kù)連接屬性和 SQL 語(yǔ)句,
MyBatis 會(huì)處理這些底層工作。
另外,MyBatis 還提供了其他的一些特性來簡(jiǎn)化持久化邏輯的實(shí)現(xiàn):
- 它支持復(fù)雜的SQL結(jié)果集數(shù)據(jù)映射到嵌套對(duì)象圖結(jié)構(gòu)
- 它支持一對(duì)一和一對(duì)多的結(jié)果集和Java對(duì)象的映射
- 它支持根據(jù)輸入的數(shù)據(jù)構(gòu)建動(dòng)態(tài)的SQL語(yǔ)句
1.2 mybatis的安裝和配置
1.2.1 新建一個(gè)maven項(xiàng)目,在pom.xml添加依賴
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.22</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
1.2.2 新建students表,插入測(cè)試數(shù)據(jù)
CREATE TABLE `students` (
`stud_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`email` varchar(50) NOT NULL,
`dob` date DEFAULT NULL,
PRIMARY KEY (`stud_id`),
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
insert into students(`name`,email,dob) values ('John','john@gmail.com','1990-09-09');
insert into tutors(`name`,email,dob) values ('Ying','ying@gmail.com','1990-09-09');
- 新建log4j.properties文件到resources文件夾
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n
1.2.3 新建mybatis-config.xml和StudentMapper.xml配置文件
mybatis-config.xml包括數(shù)據(jù)庫(kù)連接信息,類型別名等等;StudentMapper.xml包含了映射的 SQL 語(yǔ)句
- 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>
<typeAliases>
<typeAlias alias="Student" type="com.mrq.domain.Student"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="1l9o9v0e"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/mrq/mappers/StudentMapper.xml" />
</mappers>
</configuration>
- StudentMapper.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="com.mrq.mappers.StudentMapper">
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id"></id>
<result property="name" column="name"></result>
<result property="email" column="email"></result>
<result property="dob" column="dob"></result>
</resultMap>
<select id="findAllStudents" resultMap="StudentResult">
SELECT * FROM STUDENTS
</select>
<select id="findStudentById" parameterType="int" resultType="Student">
SELECT STUD_ID AS STUDID,NAME,EMAIL,DOB
FROM STUDENTS WHERE STUD_ID=#{Id}
</select>
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB)
VALUES(#{studId},#{name},#{email},#{dob}))
</insert>
</mapper>
StudentMapper,xml 文件包含的映射的 SQL 語(yǔ)句可以通過 ID 加上名空間調(diào)用。
1.2.4 新建 MyBatisSqlSessionFactory 單例類 使其持有一個(gè) SqlSessionFactory 單例對(duì)象:
package com.mrq.util;
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;
public class MyBatisSqlSessionFactory {
private static SqlSessionFactory sqlSessionFactory;
private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();
static{
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
System.out.println(inputStream+"---->");
} catch (IOException e) {
// TODO: handle exception
e.getSuppressed();
throw new RuntimeException(e);
}
}
public static SqlSession openSession() {
SqlSession sqlSession = threadLocal.get();
if (sqlSession==null) {
sqlSession = sqlSessionFactory.openSession();
threadLocal.set(sqlSession);
}
return sqlSession;
}
public static void closeSqlSession() {
SqlSession sqlSession = threadLocal.get();
if (sqlSession!=null) {
sqlSession.close();
threadLocal.remove();
}
}
private MyBatisSqlSessionFactory() {
// TODO Auto-generated constructor stub
}
}
1)在靜態(tài)初始化塊中加載mybatis配置文件和StudentMapper.xml文件一次
2)使用ThreadLocal對(duì)象讓當(dāng)前線程與SqlSession對(duì)象綁定在一起
3)獲取當(dāng)前線程中的SqlSession對(duì)象,如果沒有的話,從SqlSessionFactory對(duì)象中獲取SqlSession對(duì)象
4)獲取當(dāng)前線程中的SqlSession對(duì)象,再將其關(guān)閉,釋放其占用的資源
1.2.5 新建StudentMapper接口和StudentService類
StudentMapper.java
package com.mrq.mappers;
import java.util.List;
import com.mrq.domain.Student;
public interface StudentMapper {
List<Student> findAllStudents();
Student findStudentById(Integer id);
void insertStudent(Student student);
}
StudentService.java
package com.mrq.service;
import static org.hamcrest.CoreMatchers.nullValue;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mrq.domain.Student;
import com.mrq.mappers.StudentMapper;
import com.mrq.util.MyBatisSqlSessionFactory;
public class StudentService {
private Logger logger = LoggerFactory.getLogger(getClass());
public List<Student> findAllStudents() {
SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.findAllStudents();
} finally {
sqlSession.close();
}
}
public Student findStudentById(Integer studId) {
logger.debug("Select Student by Id:{}",studId);
SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.findStudentById(studId);
} finally {
// TODO: handle finally clause
sqlSession.close();
}
}
public void createStudent(Student student) {
SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
studentMapper.insertStudent(student);
sqlSession.commit();
} finally {
// TODO: handle finally clause
sqlSession.close();
}
}
}
也可以通過sqlSession的api執(zhí)行SQL語(yǔ)句
Student student = (Student)sqlSession.
selectOne("com.mrq.mappers.StudentMapper.findStudentById",
studId);
1.2.6 創(chuàng)建測(cè)試Service類測(cè)試
private static StudentService studentService;
static{
studentService = new StudentService();
}
@Test
public void testFindAllStudents() {
List<Student> students = studentService.findAllStudents();
Assert.assertNotNull(students);
for (Student student : students) {
System.out.println(student);
}
}
@Test
public void testFindStudentById()
{
Student student = studentService.findStudentById(5);
Assert.assertNotNull(student);
System.out.println(student);
}
@Test
public void testCreateStudent()
{
Student student = new Student();
int id = 5;
student.setStudId(id);
student.setName("student_" + id);
student.setEmail("student_" + id + "@didihu.com");
student.setDob(new Date());
studentService.createStudent(student);
Student newStudent = studentService.findStudentById(id);
Assert.assertNotNull(newStudent);
}
1.2.7 mybatis的工作流程
首先,我們配置了 MyBatis 最主要的配置文件-mybatis-config.xml,里面包含了 JDBC 連接參數(shù);配置了映射器Mapper XML 配置文件文件,里面包含了 SQL 語(yǔ)句的映射。
我們使用 mybatis-config.xml 內(nèi)的信息創(chuàng)建了 SqlSessionFactory 對(duì)象。每個(gè)數(shù)據(jù)庫(kù)環(huán)境應(yīng)該就一個(gè)
SqlSessionFactory 對(duì)象實(shí)例,所以我們使用了單例模式只創(chuàng)建一個(gè) SqlSessionFactory 實(shí)例。
我們創(chuàng)建了一個(gè)映射器 Mapper 接口-StudentMapper,其定義的方法簽名和在 StudentMapper.xml 中定義的完全 一樣(即映射器 Mapper 接口中的方法名跟 StudentMapper.xml 中的 id 的值相同)。注意 StudentMapper.xml 中 namespace 的值被設(shè)置成 com.mrq.mappers.StudentMapper,是 StudentMapper 接口的完全限定名。這使我們 可以使用接口來調(diào)用映射的 SQL 語(yǔ)句。
在 StudentService.java 中,我們?cè)诿恳粋€(gè)方法中創(chuàng)建了一個(gè)新的 SqlSession,并在方法功能完成后關(guān)閉 SqlSession。每一個(gè)線程應(yīng)該有它自己的 SqlSession 實(shí)例。SqlSession 對(duì)象實(shí)例不是線程安全的,并且不被共享。所 以 SqlSession 的作用域最好就是其所在方法的作用域。從 Web 應(yīng)用程序角度上看,SqlSession 應(yīng)該存在于 request 級(jí) 別作用域上。
二. mybatis配置主要內(nèi)容
MyBatis 最關(guān)鍵的組成部分是 SqlSessionFactory,我們可以從中獲取 SqlSession,并執(zhí)行映射的 SQL 語(yǔ)句。
SqlSessionFactory 對(duì)象可以通過基于 XML 的配置信息或者 Java API 創(chuàng)建。
我們將探索各種 MaBatis 配置元素,如 dataSource,environments,全局參數(shù)設(shè)置,typeAlias,typeHandlers,
SQL 映射;接著我們將實(shí)例化 SqlSessionFactory。
2.1 使用 XML 配置 MyBatis
構(gòu)建 SqlSessionFactory 最常見的方式是基于 XML 配置(的構(gòu)造方式)。下面的 mybatis-config.xml 展示了一個(gè) 典型的 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>
<properties resource="application.properties">
<property name="jdbc.username" value="root"></property>
<property name="jdbc.password" value="1l9o9v0e"/>
</properties>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<typeAlias alias="Student" type="com.mrq.domain.Student"/>
<typeAlias type="com.mrq.domain.Tutor" alias="Tutor" />
<typeAlias type="com.mrq.domain.Course" alias="Course" />
<package name="com.mrq.domain"/>
</typeAliases>
<typeHandlers>
<typeHandler handler="com.mrq.typehandlers.PhoneTypeHandler"/>
<package name="com.mrq.typehandlers"/>
</typeHandlers>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
</transactionManager>
<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>
<!-- <environment id="production">
<transactionManager type="MANAGED"></transactionManager>
<dataSource type="JNDI">
<property name="data_source" value="java:comp/jdbc/mybatis02DS"/>
</dataSource>
</environment> -->
</environments>
<mappers>
<!-- <mapper resource="com/mrq/mappers/StudentMapper.xml" /> -->
<mapper resource="com/mrq/mappers/TutorMapper.xml" />
<!-- <mapper class="com.mrq.mappers.TutorMapper" />
--> </mappers>
</configuration>
下面讓我們逐個(gè)討論上述配置文件的組成部分,先從最重要的部分開始,即 environments:
2.1.1 environment
MyBatis 支持配置多個(gè) dataSource 環(huán)境,可以將應(yīng)用部署到不同的環(huán)境上,如 DEV(開發(fā)環(huán)境),TEST(測(cè)試換將), QA(質(zhì)量評(píng)估環(huán)境),UAT(用戶驗(yàn)收環(huán)境),PRODUCTION(生產(chǎn)環(huán)境),可以通過將默認(rèn) environment 值設(shè)置成想要的 environment id 值。
在上述的配置中,默認(rèn)的環(huán)境 environment 被設(shè)置成 development。當(dāng)需要將程序部署到生產(chǎn)服務(wù)器上時(shí),你不需 要修改什么配置,只需要將默認(rèn)環(huán)境environment值設(shè)置成生產(chǎn)環(huán)境的 environmentid屬性即可。
有時(shí)候,我們可能需要在相同的應(yīng)用下使用多個(gè)數(shù)據(jù)庫(kù)。比如我們可能有 SHOPPING-CART 數(shù)據(jù)庫(kù)來存儲(chǔ)所有的訂單 明細(xì);使用 REPORTS 數(shù)據(jù)庫(kù)存儲(chǔ)訂單明細(xì)的合計(jì),用作報(bào)告。
如果你的應(yīng)用需要連接多個(gè)數(shù)據(jù)庫(kù),你需要將每個(gè)數(shù)據(jù)庫(kù)配置成獨(dú)立的環(huán)境,并且為每一個(gè)數(shù)據(jù)庫(kù)創(chuàng)建一個(gè) SqlSessionFactory。
<environments default="shoppingcart">
<environment id="shoppingcart">
<transactionManager type="MANAGED" />
<dataSource type="JNDI">
<property name="data_source" value="java:comp/jdbc/ ShoppingcartDS" />
</dataSource>
</environment>
<environment id="reports">
<transactionManager type="MANAGED" />
<dataSource type="JNDI">
<property name="data_source" value="java:comp/jdbc/ReportsDS" />
</dataSource>
</environment>
</environments>
我們可以如下為每個(gè)環(huán)境創(chuàng)建一個(gè) SqlSessionFactory:
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
defaultSqlSessionFactory = new SqlSessionFactoryBuilder().
build(inputStream);
cartSqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "shoppingcart");
reportSqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "reports");
創(chuàng)建 SqlSessionFactory 時(shí),如果沒有明確指定環(huán)境 environment id,則會(huì)使用默認(rèn)的環(huán)境 environment 來創(chuàng) 建。在上述的源碼中,默認(rèn)的 SqlSessionFactory 便是使用 shoppingcart 環(huán)境設(shè)置創(chuàng)建的。
對(duì)于每個(gè)環(huán)境 environment,我們需要配置 dataSource 和 transactionManager 元素。
2.1.2 數(shù)據(jù)源DataSource
dataSource 元素被用來配置數(shù)據(jù)庫(kù)連接屬性。
<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>
- 使用連接池
<!--定義一個(gè)jdbc數(shù)據(jù)源,創(chuàng)建一個(gè)驅(qū)動(dòng)管理數(shù)據(jù)源的bean -->
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.url}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="acquireIncrement" value="5"></property>
<property name="initialPoolSize" value="10"></property>
<property name="minPoolSize" value="5"></property>
<property name="maxPoolSize" value="20"></property>
</bean>
dataSource 的類型可以配置成其內(nèi)置類型之一,如 UNPOOLED,POOLED,JNDI。
如果將類型設(shè)置成UNPOOLED,MyBatis會(huì)為每一個(gè)數(shù)據(jù)庫(kù)操作創(chuàng)建一個(gè)新的連接,并關(guān)閉它。該方式適用于只有小規(guī)模數(shù)量并發(fā)用戶的簡(jiǎn)單應(yīng)用程序上。
如果將屬性設(shè)置成POOLED,MyBatis會(huì)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接池,連接池中的一個(gè)連接將會(huì)被用作數(shù)據(jù)庫(kù)操作。一旦數(shù)據(jù)庫(kù)操作完成,MyBatis 會(huì)將此連接返回給連接池。在開發(fā)或測(cè)試環(huán)境中,經(jīng)常使用此
種方式。如果將類型設(shè)置成JNDI,MyBatis從在應(yīng)用服務(wù)器向配置好的JNDI數(shù)據(jù)源dataSource獲取數(shù)據(jù)庫(kù)連接。在生產(chǎn)環(huán)境中,優(yōu)先考慮這種方式。
2.1.3 事務(wù)管理器 TransactionManager
MyBatis 支持兩種類型的事務(wù)管理器: JDBC and MANAGED.
JDBC事務(wù)管理器被用作當(dāng)應(yīng)用程序負(fù)責(zé)管理數(shù)據(jù)庫(kù)連接的生命周期(提交、回退等等)的時(shí)候。當(dāng)你將TransactionManager 屬性設(shè)置成 JDBC,MyBatis 內(nèi)部將使用 JdbcTransactionFactory 類創(chuàng)建TransactionManager。例如,部署到 Apache Tomcat 的應(yīng)用程序,需要應(yīng)用程序自己管理事務(wù)。
MANAGED 事務(wù)管理器是當(dāng)由應(yīng)用服務(wù)器負(fù)責(zé)管理數(shù)據(jù)庫(kù)連接生命周期的時(shí)候使用。當(dāng)你將 TransactionManager 屬性設(shè)置成 MANAGED 時(shí),MyBatis 內(nèi)部使用 ManagedTransactionFactory 類
創(chuàng)建事務(wù)管理器TransactionManager。例如,當(dāng)一個(gè)JavaEE的應(yīng)用程序部署在類似 JBoss,WebLogic, GlassFish 應(yīng)用服務(wù)器上時(shí),它們會(huì)使用 EJB 進(jìn)行應(yīng)用服務(wù)器的事務(wù)管理能力。在這些管理環(huán)境中,你 可以使用 MANAGED 事務(wù)管理器。
(譯者注:Managed 是托管的意思,即是應(yīng)用本身不去管理事務(wù),而是把事務(wù)管理交給應(yīng)用所在的服務(wù) 器進(jìn)行管理。)
2.1.4 屬性properties
屬性配置元素可以將配置值具體化到一個(gè)屬性文件中,并且使用屬性文件的 key 名作為占位符。在上述的配置中,我 們將數(shù)據(jù)庫(kù)連接屬性具體化到了 application.properties 文件中,并且為 driver,URL 等屬性使用了占位符。
- 在 applications.properties 文件中配置數(shù)據(jù)庫(kù)連接參數(shù),如下所示:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis02
jdbc.username=root
jdbc.password=1l9o9v0e
- 在 mybatis-config.xml 文件中,為屬性使用 application.properties 文件中定義的占位符:
<properties resource="application.properties">
<property name="jdbc.username" value="db_user"></property>
<property name="jdbc.password" value="verysecurepwd"/>
</properties>
<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>
并且,你可以在<properties>元素中配置默認(rèn)參數(shù)的值。如果<properties>中定義的元素和屬性文件定義元素的 key 值相同,它們會(huì)被屬性文件中定義的值覆蓋。
這里,如果 application.properties 文件包含值 jdbc.username 和 jdbc.password,則上述定義的 username 和 password 的值 db_user 和 verysecurepwd 將會(huì)被 application.properties 中定義的對(duì)應(yīng)的 jdbc.username 和 jdbc.password 值覆蓋。
2.1.5 類型別名 typeAliases:
在 SQLMapper 配置文件中,對(duì)于 resultType 和 parameterType 屬性值,我們需要使用 JavaBean 的完全限定名。
<typeAliases>
<typeAlias alias="Student" type="com.mrq.domain.Student"/>
<typeAlias type="com.mrq.domain.Tutor" alias="Tutor" />
<typeAlias type="com.mrq.domain.Course" alias="Course" />
<package name="com.mrq.domain"/>
</typeAliases>
這里我們?yōu)?resultType 和 parameterType 屬性值設(shè)置為 Student 類型的完全限定名: com.mrq.domain.Student
我們可以為完全限定名取一個(gè)別名(alias),然后其需要使用完全限定名的地方使用別名,而不是到處使用完全限定 名。如下例子所示,為完全限定名起一個(gè)別名.然后在 SQL Mapper 映射文件中, 如下使用 Student 的別名:
<select id="findStudentById" parameterType="int" resultType="Student">
SELECT STUD_ID AS STUDID,NAME,EMAIL,DOB
FROM STUDENTS WHERE STUD_ID=#{Id}
</select>
<update id="updateStudent" parameterType="Student">
update students set name=#{name},email=#{email},phone=#{phone}
where stud_id=#{studId}
</update>
我們可以不用為每一個(gè)JavaBean單獨(dú)定義別名, 你可以為提供需要取別名的JavaBean所在的包(package),MyBatis 會(huì)自動(dòng)掃描包內(nèi)定義的 JavaBeans,然后分別為 JavaBean 注冊(cè)一個(gè)小寫字母開頭的非完全限定的類名形式的別名。如下所 示,提供一個(gè)需要為 JavaBeans 起別名的包名:
<typeAliases>
<package name="com.mrq.domain"/>
</typeAliases>
如果 Student.java 和 Tutor.java Bean 定義在 com.mrq.domain 包中,則 com.mrq.domain.Student 的別名會(huì)被注冊(cè)為 student。而 com.mrq.domain.Tutor 別名將會(huì)被注冊(cè)為 tutor.
還有另外一種方式為 JavaBeans 起別名,使用注解@Alias:
@Alias("StudentAlias")
public class Student
{
}
@Alias 注解將會(huì)覆蓋配置文件中的<typeAliases>定義。
2.1.6 類型處理器 typeHandlers
如上一章已經(jīng)討論過,MyBatis 通過抽象 JDBC 來簡(jiǎn)化了數(shù)據(jù)持久化邏輯的實(shí)現(xiàn)。MyBatis 在其內(nèi)部使用 JDBC,提供了更簡(jiǎn)潔的方式實(shí)現(xiàn)了數(shù)據(jù)庫(kù)操作。
當(dāng) MyBatis 將一個(gè) Java 對(duì)象作為輸入?yún)?shù)執(zhí)行 INSERT 語(yǔ)句操作時(shí),它會(huì)創(chuàng)建一個(gè) PreparedStatement 對(duì)象,并且 使用 setXXX()方式對(duì)占位符設(shè)置相應(yīng)的參數(shù)值。
這里,XXX 可以是 Int,String,Date 等 Java 對(duì)象屬性類型的任意一個(gè)。示例如下:
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB)
VALUES(#{studId},#{name},#{email},#{dob})
</insert>
為執(zhí)行這個(gè)SQL語(yǔ)句,mybatis將會(huì)執(zhí)行下面的步驟
- 創(chuàng)建一個(gè)有占位符的PreparedStatement 接口,如下:
PreparedStatement pstmt = connection.prepareStatement("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB) VALUES(?,?,?,?)");
- 檢查Student對(duì)象的屬性studId的類型,然后使用合適setXXX方法去設(shè)置參數(shù)值。這里studId是integer 類型,所以會(huì)使用 setInt()方法:
pstmt.setInt(1,student.getStudId());
- 類似地,對(duì)于name和email屬性都是String類型,MyBatis使用setString()方法設(shè)置參數(shù)。
pstmt.setString(2, student.getName());
pstmt.setString(3, student.getEmail());
4.對(duì)于dob屬性,MyBatis會(huì)使用setDate()方法設(shè)置dob處占位符位置的值。
5.MyBaits 會(huì)將 java.util.Date 類型轉(zhuǎn)換為 java.sql.Timestamp 并設(shè)值:
但 MyBatis 是怎么知道對(duì)于 Integer 類型屬性使用 setInt() 和 String 類型屬性使用 setString()方法呢? 其實(shí) MyBatis 是通過使用類型處理器(type handlers)來決定這么做的。
MyBatis 對(duì)于以下的類型使用內(nèi)建的類型處理器:所有的基本數(shù)據(jù)類型、基本類型的包裹類型、byte[]、 java.util.Date、java.sql.Date、java,sql.Time、java.sql.Timestamp、java 枚舉類型等。所以當(dāng) MyBatis 發(fā)現(xiàn) 屬性的類型屬于上述類型,他會(huì)使用對(duì)應(yīng)的類型處理器將值設(shè)置到 PreparedStatement 中,同樣地,當(dāng)從 SQL 結(jié)果集構(gòu) 建 JavaBean 時(shí),也有類似的過程.
那如果我們給了一個(gè)自定義的對(duì)象類型,來存儲(chǔ)存儲(chǔ)到數(shù)據(jù)庫(kù)呢?示例如下:
假設(shè)表STUDENTS有一個(gè)PHONE字段,類型為VARCHAR(15),而JavaBean Student有一個(gè)PhoneNumber類定義類 型的 phoneNumber 屬性
package com.mrq.domain;
public class PhoneNumber {
private String countryCode;
private String stateCode;
private String number;
public PhoneNumber() {
// TODO Auto-generated constructor stub
}
public PhoneNumber(String countryCode, String stateCode, String number) {
super();
this.countryCode = countryCode;
this.stateCode = stateCode;
this.number = number;
}
public PhoneNumber(String string){
if (string!=null) {
String[] parts = string.split("-");
if (parts.length>0) {
this.countryCode = parts[0];
}
if (parts.length>1) {
this.stateCode = parts[1];
}
if (parts.length>2) {
this.number = parts[2];
}
}
}
public String getAsString() {
return countryCode+"-"+stateCode+"-"+number;
}
public String getCountryCode() {
return countryCode;
}
public void setCountryCode(String countryCode) {
this.countryCode = countryCode;
}
public String getStateCode() {
return stateCode;
}
public void setStateCode(String stateCode) {
this.stateCode = stateCode;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
public class Student
{
private Integer id;
private String name;
private String email;
private PhoneNumber phone;
// Setters and getters
}
mappeing
<insert id="insertStudent" parameterType="Student">
insert into students(name,email,phone)
values(#{name},#{email},#{phone})
</insert>
這里,phone 參數(shù)需要傳遞給#{phone};而 phone 對(duì)象是 PhoneNumber 類型。然而,MyBatis 并不知道該怎樣來處 理這個(gè)類型的對(duì)象。
為了讓 MyBatis 明白怎樣處理這個(gè)自定義的 Java 對(duì)象類型,如 PhoneNumber,我們可以創(chuàng)建一個(gè)自定義的類型處理 器,如下所示:
- MyBatis 提供了抽象類 BaseTypeHandler<T> ,我們可以繼承此類創(chuàng)建自定義類型處理器。
package com.mrq.handlers;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import com.mrq.domain.PhoneNumber;
public class PhoneTypeHandler extends BaseTypeHandler<PhoneNumber>{
@Override
public PhoneNumber getNullableResult(ResultSet rs, String columnName) throws SQLException {
// TODO Auto-generated method stub
return new PhoneNumber(rs.getString(columnName));
}
@Override
public PhoneNumber getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
// TODO Auto-generated method stub
return new PhoneNumber(rs.getString(columnIndex));
}
@Override
public PhoneNumber getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
// TODO Auto-generated method stub
return new PhoneNumber(cs.getString(columnIndex));
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, PhoneNumber parameter, JdbcType jdbcType)
throws SQLException {
// TODO Auto-generated method stub
ps.setString(i, parameter.getAsString());
}
}
2.我們使用ps.setString()和rs.getString()方法是因?yàn)閜hone列是VARCHAR類型。
3.一旦我們實(shí)現(xiàn)了自定義的類型處理器,我們需要在mybatis-config.xml中注冊(cè)它:
<typeHandlers>
<typeHandler handler="com.mrq.typehandlers.PhoneTypeHandler"/>
<package name="com.mrq.typehandlers"/>
</typeHandlers>
注冊(cè) PhoneTypeHandler 后, MyBatis 就能夠?qū)?Phone 類型的對(duì)象值存儲(chǔ)到 VARCHAR 類型的列上。
2.1.7 全局參數(shù)設(shè)置 Settings
為滿足應(yīng)用特定的需求,MyBatis 默認(rèn)的全局參數(shù)設(shè)置可以被覆蓋(overridden)掉,如下所示:
<settings>
<setting name="cacheEnabled" value="true" />
<setting name="lazyLoadingEnabled" value="true" />
<setting name="multipleResultSetsEnabled" value="true" />
<setting name="useColumnLabel" value="true" />
<setting name="useGeneratedKeys" value="false" />
<setting name="autoMappingBehavior" value="PARTIAL" />
<setting name="defaultExecutorType" value="SIMPLE" />
<setting name="defaultStatementTimeout" value="25000" />
<setting name="safeRowBoundsEnabled" value="false" />
<setting name="mapUnderscoreToCamelCase" value="false" />
<setting name="localCacheScope" value="SESSION" />
<setting name="jdbcTypeForNull" value="OTHER" />
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode ,toString" />
</settings>
2.1.8 SQL映射定義Mappers
Mapper XML 文件中包含的 SQL 映射語(yǔ)句將會(huì)被應(yīng)用通過使用其 statementid 來執(zhí)行。我們需要在 mybatis- config.xml 文件中配置 SQL Mapper 文件的位置。
<mappers>
<mapper resource="com/mrq/mappers/StudentMapper.xml" /> <mapper url="file:///D:/mybatisdemo/app/mappers/TutorMapper.xml" /> <mapper class="com.mrq.mappers.TutorMapper" />
<package name="com.mrq.mappers" />
</mappers>
以上每一個(gè)<mapper> 標(biāo)簽的屬性有助于從不同類型的資源中加載映射 mapper:
- resource屬性用來指定在classpath中的mapper文件。
- url屬性用來通過完全文件系統(tǒng)路徑或者webURL地址來指向mapper文件
- class屬性用來指向一個(gè)mapper接口
- package屬性用來指向可以找到Mapper接口的包名
三. 使用 XML 配置 SQL 映射器
關(guān)系型數(shù)據(jù)庫(kù)和SQL是經(jīng)受時(shí)間考驗(yàn)和驗(yàn)證的數(shù)據(jù)存儲(chǔ)機(jī)制。和其他的ORM 框架如Hibernate不同,MyBatis鼓勵(lì) 開發(fā)者可以直接使用數(shù)據(jù)庫(kù),而不是將其對(duì)開發(fā)者隱藏,因?yàn)檫@樣可以充分發(fā)揮數(shù)據(jù)庫(kù)服務(wù)器所提供的 SQL 語(yǔ)句的巨大威 力。與此同時(shí),MyBaits 消除了書寫大量冗余代碼的痛苦,它使使用 SQL 更容易。
在代碼里直接嵌套 SQL 語(yǔ)句是很差的編碼實(shí)踐,并且維護(hù)起來困難。MyBaits 使用了映射器配置文件或注解來配置 SQL 語(yǔ)句。下面,我們會(huì)看到具體怎樣使用映射器配置文件來配置映射 SQL 語(yǔ)句。
3.1 映射配置文件和映射接口
在前幾章中,我們已經(jīng)看見了一些在映射器配置文件中配置基本的映射語(yǔ)句,以及怎樣使用 SqlSession 對(duì)象調(diào)用它們 的例子。
現(xiàn)在讓我們看一下在 com.mrq.mappers 包中的 StudentMapper.xml 配置文件內(nèi),是如何配置 id 為” findStudentById”的 SQL 語(yǔ)句的,代碼如下:
<?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.mrq.mappers.StudentMapper">
<select id="findStudentById" parameterType="int" resultType="Student"> select stud_id as studId, name, email, dob
from Students where stud_id=#{studId}
</select>
</mapper>
我們可以通過下列代碼調(diào)用 findStudentById 映射的 SQL 語(yǔ)句:
public Student findStudentById(Integer studId)
{
SqlSession sqlSession = MyBatisUtil.getSqlSession();
try{
Student student =
sqlSession.selectOne("com.mrq.mappers.StudentMapper.
findStudentById", studId);
return student;
}
finally{
sqlSession.close();
}
}
MyBatis 通過使用映射器 Mapper 接口提供了更好的調(diào)用映射語(yǔ)句的方法。一旦我們通過映射器配置文件配置了映射語(yǔ) 句,我們可以創(chuàng)建一個(gè)完全對(duì)應(yīng)的一個(gè)映射器接口,接口名跟配置文件名相同,接口所在包名也跟配置文件所在包名完全 一 樣(如 StudentMapper.xml 所 在 的 包 名 是 com.mrq.mappers, 對(duì) 應(yīng) 的 接 口 名 就 是com.mrq.mappers.StudentMapper.java )。映射器接口中的方法簽名也跟映射器配置文件中完全對(duì)應(yīng):方法名為 配置文件中 id 值;方法參數(shù)類型為 parameterType 對(duì)應(yīng)值;方法返回值類型為 returnType 對(duì)應(yīng)值。
對(duì)于上述的 StudentMapper.xml 文件,我們可以創(chuàng)建一個(gè)映射器接口 StudentMapper.java 如下:
package com.mrq.mappers;
public interface StudentMapper
{
Student findStudentById(Integer id);
}
在StudentMapper.xml 映射器配置文件中,其名空間namespace 應(yīng)該跟StudentMapper接口的完全限定名保持一 致。另外,StudentMapper.xml 中語(yǔ)句 id,parameterType,returnType 應(yīng)該分別和 StudentMapper 接口中的方法名, 參數(shù)類型,返回值相對(duì)應(yīng).
使用映射器接口我們可以以類型安全的形式調(diào)用調(diào)用映射語(yǔ)句。如下所示:
public Student findStudentById(Integer studId) {
logger.debug("Select Student by Id:{}",studId);
SqlSession sqlSession = MyBatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.findStudentById(studId);
} finally {
// TODO: handle finally clause
sqlSession.close();
}
}
tip:
即使映射器 Mapper 接口可以以類型安全的方式調(diào)用映射語(yǔ)句,但是我們我 負(fù)責(zé)書寫正確的,匹配方法名、參數(shù)類型、和返回值的映射器 Mapper 接口。 如果映射器 Mapper 接口中的方法和 XML 中的映射語(yǔ)句不能匹配,會(huì)在運(yùn)行期 拋出一個(gè)異常。實(shí)際上,指定 parameterType 是可選的;MyBatis 可以使用反 射機(jī)制來決定 parameterType。但是,從配置可讀性的角度來看,最好指定 parameterType 屬性。如果 parameterType 沒有被提及,開發(fā)者必須查看 Mapper XML 配置和 Java 代碼了解傳遞給語(yǔ)句的輸入?yún)?shù)的數(shù)據(jù)類
3.2 映射語(yǔ)句
MyBatis 提供了多種元素來配置不同類型的語(yǔ)句,如 SELECT,INSERT,UPDATE,DELETE。接下來讓我們看看如何具體配置映射語(yǔ)句.
3.2.1 insert 語(yǔ)句:一個(gè) INSERT SQL 語(yǔ)句可以在<insert>元素在映射器 XML 配置文件中配置,如下所示:
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE)
VALUES(#{studId},#{name},#{email},#{phone})
</insert>
這里我們使用一個(gè)ID insertStudent,可以在名空間com.mrq.mappers.StudentMapper.insertStudent中 唯一標(biāo)識(shí)。parameterType 屬性應(yīng)該是一個(gè)完全限定類名或者是一個(gè)類型別名(alias)。
我們可以如下調(diào)用這個(gè)語(yǔ)句:
int count =
sqlSession.insert("com.mrq.mappers.StudentMapper.insertStudent", student);
sqlSession.insert() 方法返回執(zhí)行 INSERT 語(yǔ)句后所影響的行數(shù)。
如果不使用名空間(namespace)和語(yǔ)句 id 來調(diào)用映射語(yǔ)句,你可以通過創(chuàng)建一個(gè)映射器 Mapper 接口,并以類型安 全的方式調(diào)用方法,如下所示:
package com.mrq.mappers;
public interface StudentMapper
{
int insertStudent(Student student);
}
可以如下調(diào)用 insertStudent 映射語(yǔ)句:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int count = mapper.insertStudent(student);
- 自動(dòng)生成主鍵
在上述的INSERT語(yǔ)句中,我們?yōu)榭梢宰詣?dòng)生成(auto-generated)主鍵的列 STUD_ID插入值。我們可以使用 useGeneratedKeys 和 keyProperty屬性讓數(shù)據(jù)庫(kù)生成 auto_increment列的值,并將生成的值設(shè)置到其中一個(gè) 輸入對(duì)象屬性內(nèi),如下所示:
<insert id="insertStudent" parameterType="Student" useGeneratedKeys="true"
keyProperty="studId">
INSERT INTO STUDENTS(NAME, EMAIL, PHONE) VALUES(#{name},#{email},#{phone})
</insert>
這里 STUD_ID 列值將會(huì)被 MySQL 數(shù)據(jù)庫(kù)自動(dòng)生成,并且生成的值會(huì)被設(shè)置到 student 對(duì)象的 studId 屬性上。
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
mapper.insertStudent(student);
現(xiàn)在可以如下獲取插入的 STUDENT 記錄的 STUD_ID 的值:
int studentId = student.getStudId();
有些數(shù)據(jù)庫(kù)如 Oracle 并不支持 AUTO_INCREMENT 列,其使用序列(SEQUENCE)來生成主鍵值。 假設(shè)我們有一個(gè)名為 STUD_ID_SEQ 的序列來生成 SUTD_ID 主鍵值。使用如下代碼來生成主鍵:
<insert id="insertStudent" parameterType="Student">
<selectKey keyProperty="studId" resultType="int" order="BEFORE">
SELECT ELEARNING.STUD_ID_SEQ.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE)
VALUES(#{studId},#{name},#{email},#{phone})
</insert>
這里我們使用了<selectKey>子元素來生成主鍵值,并將值保存到 Student 對(duì)象的 studId 屬性上。 屬性 order=“before”表示 MyBatis 將取得序列的下一個(gè)值作為主鍵值,并且在執(zhí)行 INSERT SQL 語(yǔ)句之前將值設(shè)置到 studId 屬性上。
我們也可以在獲取序列的下一個(gè)值時(shí),使用觸發(fā)器(trigger)來設(shè)置主鍵值,并且在執(zhí)行INSERT SQL語(yǔ)句之 前將值設(shè)置到主鍵列上。如果你采取這樣的方式,則對(duì)應(yīng)的 INSERT 映射語(yǔ)句如下所示:
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(NAME,EMAIL, PHONE)
VALUES(#{name},#{email},#{phone})
<selectKey keyProperty="studId" resultType="int" order="AFTER">
SELECT ELEARNING.STUD_ID_SEQ.CURRVAL FROM DUAL
</selectKey>
</insert>
3.2.2 UPDATE 語(yǔ)句
一個(gè) UPDATE SQL 語(yǔ)句可以在<update>元素在映射器 XML 配置文件中配置,如下所示:
<update id="updateStudent" parameterType="Student">
UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email}, PHONE=#{phone}
WHERE STUD_ID=#{studId}
</update>
可以如下調(diào)用此語(yǔ)句:
int noOfRowsUpdated =
sqlSession.update("com.mrq.mappers.StudentMapper.updateStudent", student);
sqlSession.update() 方法返回執(zhí)行 UPDATE 語(yǔ)句之后影響的行數(shù)。
如果不使用名空間(namespace)和語(yǔ)句 id 來調(diào)用映射語(yǔ)句,你可以通過創(chuàng)建一個(gè)映射器 Mapper 接口,并以類型 安全的方式調(diào)用方法,如下所示:
public interface StudentMapper
{
int updateStudent(Student student);
}
可以使用映射器 Mapper 接口來調(diào)用 updateStudent 語(yǔ)句,如下所示:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int noOfRowsUpdated = mapper.updateStudent(student);
3.2.3 刪除語(yǔ)句
一個(gè) DELETE SQL 語(yǔ)句可以在<delete>元素在映射器 XML 配置文件中配置,如下所示:
<delete id="deleteStudent" parameterType="int">
DELETE FROM STUDENTS WHERE STUD_ID=#{studId}
</delete>
可以如下調(diào)用此語(yǔ)句:
int studId = 1;
int noOfRowsDeleted =
sqlSession.delete("com.mrq.mappers.StudentMapper.deleteStudent", studId);
sqlSession.delete() 方法返回 delete 語(yǔ)句執(zhí)行后影響的行數(shù)。
如果不使用名空間(namespace)和語(yǔ)句 id 來調(diào)用映射語(yǔ)句,你可以通過創(chuàng)建一個(gè)映射器 Mapper 接口,并以類型安 全的方式調(diào)用方法,如下所示:
public interface StudentMapper
{
int deleteStudent(int studId);
}
可以使用映射器 Mapper 接口來調(diào)用 deleteStudent 語(yǔ)句,如下所示:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int noOfRowsDeleted = mapper.deleteStudent(studId);
3.2.4 select 語(yǔ)句
MyBatis 真正強(qiáng)大的功能,在于映射 SELECT 查詢結(jié)果到 JavaBeans 方面的極大靈活性。 讓我們看看一個(gè)簡(jiǎn)單的 select 查詢是如何(在 MyBatis 中)配置的,如下所示:
<select id="findStudentById" parameterType="int"
resultType="Student">
SELECT STUD_ID, NAME, EMAIL, PHONE
FROM STUDENTS
WHERE STUD_ID=#{studId}
</select>
可以如下調(diào)用此語(yǔ)句:
int studId =1;
Student student = sqlSession.selectOne("com.mrq.mappers.
StudentMapper.findStudentById", studId);
不使用名空間(namespace)和語(yǔ)句 id 來調(diào)用映射語(yǔ)句,你可以通過創(chuàng)建一個(gè)映射器 Mapper 接口,并以類型安 全的方式調(diào)用方法,如下所示:
public interface StudentMapper
{
Student findStudentById(Integer studId);
}
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.findStudentById(studId);
如果你檢查 Student 對(duì)象的屬性值,你會(huì)發(fā)現(xiàn) studId 屬性值并沒有被 stud_id 列值填充。這是因?yàn)?MyBatis 自動(dòng)對(duì) JavaBean 中和列名匹配的屬性進(jìn)行填充。這就是為什么 name ,email,和 phone 屬性被填充,而 studId 屬性沒有被填 充。
為了解決這一問題,我們可以為列名起一個(gè)可以與 JavaBean 中屬性名匹配的別名,如下所示:
<select id="findStudentById" parameterType="int"
resultType="Student">
SELECT STUD_ID AS studId, NAME,EMAIL, PHONE
FROM STUDENTS
WHERE STUD_ID=#{studId}
</select>
現(xiàn)在,Student 這個(gè) Bean 對(duì)象中的值將會(huì)恰當(dāng)?shù)乇?stud_id,name,email,phone 列填充了。 現(xiàn)在,讓我們看一下如何執(zhí)行返回多條結(jié)果的 SELECT 語(yǔ)句查詢,如下所示:
<select id="findAllStudents" resultType="Student">
SELECT STUD_ID AS studId, NAME,EMAIL, PHONE
FROM STUDENTS
</select>
List<Student> students =
sqlSession.selectList("com.mrq.mappers.StudentMapper.findAllStudents");
映射器 Mapper 接口 StudentMapper 可以如下定義:
public interface StudentMapper
{
List<Student> findAllStudents();
}
可以如下調(diào)用 findAllStudents 語(yǔ)句:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.findAllStudents();
如果你注意到上述的 SELECT 映射定義,你可以看到,我們?yōu)樗械挠成湔Z(yǔ)句中的 stud_id 起了別名。我們可以使用 ResultMaps,來避免上述的到處重復(fù)別名。我們稍后會(huì)繼續(xù)討論。
除了 java.util.List,你也可以是由其他類型的集合類,如 Set,Map,以及(SortedSet)。MyBatis 根據(jù)集合的類型,會(huì)采用適當(dāng)?shù)募蠈?shí)現(xiàn),如下所示:
- 對(duì)于 List,Collection,Iterable 類型,MyBatis 將返回 java.util.ArrayList
- 對(duì)于 Map 類型,MyBatis 將返回 java.util.HashMap
- 對(duì)于 Set 類型,MyBatis 將返回 java.util.HashSet
- 對(duì)于 SortedSet 類型,MyBatis 將返回 java.util.TreeSet
3.3 結(jié)果集映射 ResultMaps
ResultMaps被用來 將SQLSELECT語(yǔ)句的結(jié)果集映射到 JavaBeans的屬性中。我們可以定義結(jié)果集映射ResultMaps 并且在一些 SELECT 語(yǔ)句上引用 resultMap。MyBatis 的結(jié)果集映射 ResultMaps 特性非常強(qiáng)大,你可以使用它將簡(jiǎn)單的 SELECT 語(yǔ)句映射到復(fù)雜的一對(duì)一和一對(duì)多關(guān)系的 SELECT 語(yǔ)句上。
3.3.1 簡(jiǎn)單的resultMap
映射了查詢結(jié)果和 Student JavaBean 的簡(jiǎn)單的 resultMap 定義如下:
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id"></id>
<result property="name" column="name"></result>
<result property="email" column="email"></result>
<result property="dob" column="dob"></result>
<result property="phone" column="phone" />
</resultMap>
<select id="findAllStudents" resultMap="StudentResult">
SELECT * FROM STUDENTS
</select>
<select id="findStudentById" parameterType="int" resultType="Student">
SELECT STUD_ID AS STUDID,NAME,EMAIL,DOB
FROM STUDENTS WHERE STUD_ID=#{Id}
</select>
表示resultMap的StudentResult id值應(yīng)該在此名空間內(nèi)是唯一的。并且type屬性應(yīng)該是完全限定類名或者是返 回類型的別名。
<result>子元素被用來將一個(gè) resultset 列映射到 JavaBean 的一個(gè)屬性中。
<id>元素和<result>元素功能相同,不過它被用來映射到唯一標(biāo)識(shí)屬性,用來區(qū)分和比較對(duì)象(一般和主鍵列相對(duì)應(yīng))。
在<select>語(yǔ)句中,我們使用了 resultMap 屬性,而不是 resultType 來引用 StudentResult 映射。當(dāng)<select>語(yǔ) 句中配置了 resutlMap 屬性,MyBatis 會(huì)使用此數(shù)據(jù)庫(kù)列名與對(duì)象屬性映射關(guān)系來填充 JavaBean 中的屬性。
* resultType 和 resultMap 二者只能用其一,不能同時(shí)使用。
看另外一個(gè)select映射語(yǔ)句定義的例子,怎樣將查詢結(jié)果填充到 HashMap 中。如下所示:
<select id="findStudentById" parameterType="int" resultType="map">
SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}
</select>
在上述的select語(yǔ)句中,我們將 resultType 配置成 map,即 java.util.HashMap 的別名。在這種情況下,結(jié)果集 的列名將會(huì)作為 Map 中的 key 值,而列值將作為 Map 的 value 值。
HashMap<String,Object> studentMap = sqlSession.selectOne("com.
mrq.mappers.StudentMapper.findStudentById", studId);
System.out.println("stud_id :"+studentMap.get("stud_id"));
System.out.println("name :"+studentMap.get("name"));
System.out.println("email :"+studentMap.get("email"));
System.out.println("phone :"+studentMap.get("phone"));
再看一個(gè) 使用resultType=”map”,返回多行結(jié)果的例子:
<select id="findAllStudents" resultType="map">
SELECT STUD_ID, NAME, EMAIL, PHONE FROM STUDENTS
</select>
由于 resultType=”map”和語(yǔ)句返回多行,則最終返回的數(shù)據(jù)類型應(yīng)該是 "List<HashMap<String,Object>>",如下所 示:
List<HashMap<String, Object>> studentMapList =
sqlSession.selectList("com.mrq.mappers.StudentMapper.findAllS
tudents");
for(HashMap<String, Object> studentMap : studentMapList)
{
System.out.println("studId :" + studentMap.get("stud_id"));
System.out.println("name :" + studentMap.get("name"));
System.out.println("email :" + studentMap.get("email"));
System.out.println("phone :" + studentMap.get("phone"));
}
3.3.2 拓展 ResultMap
我們可以從從另外一個(gè)<resultMap>,拓展出一個(gè)新的<resultMap>,這樣,原先的屬性映射可以繼承過來,以實(shí)現(xiàn)。
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult" extends="StudentResult">
<result property="address.addrId" column="addr_id" />
<result property="address.street" column="street" />
<result property="address.city" column="city" />
<result property="address.state" column="state" />
<result property="address.zip" column="zip" />
<result property="address.country" column="country" />
</resultMap>
id 為 StudentWithAddressResult 的 resultMap 拓展了 id 為 StudentResult 的 resultMap。 如果你只想映射 Student 數(shù)據(jù),你可以使用 id 為 StudentResult 的 resultMap,如下所示:
<select id="findStudentById" parameterType="int"
resultMap="StudentResult">
SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}
</select>
如果你想將映射 Student 數(shù)據(jù)和 Address 數(shù)據(jù),你可以使用 id 為 StudentWithAddressResult 的 resultMap:
<select id="selectStudentWithAddress" parameterType="int"
resultMap="StudentWithAddressResult">
SELECT STUD_ID, NAME, EMAIL, PHONE, A.ADDR_ID, STREET, CITY,
STATE, ZIP, COUNTRY
FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON
S.ADDR_ID=A.ADDR_ID
WHERE STUD_ID=#{studId}
</select>
3.4 一對(duì)一映射
在我們的域模型樣例中,每一個(gè)學(xué)生都有一個(gè)與之關(guān)聯(lián)的地址信息。表 STUDENTS 有一個(gè) ADDR_ID 列,是 ADDRESSES 表的外鍵。
看一下怎樣取 Student 明細(xì)和其 Address 明細(xì)。
Student 和 Address 的 JavaBean 以及映射器 Mapper XML 文件定義如下所示:
public class Address
{
private Integer addrId;
private String street;
private String city;
private String state;
private String zip;
private String country;
// setters & getters
}
public class Student
{
private Integer studId;
private String name;
private String email;
private PhoneNumber phone;
private Address address;
//setters & getters
}
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
<result property="address.addrId" column="addr_id" />
<result property="address.street" column="street" />
<result property="address.city" column="city" />
<result property="address.state" column="state" />
<result property="address.zip" column="zip" />
<result property="address.country" column="country" />
</resultMap>
<select id="selectStudentWithAddress" parameterType="int"
resultMap="StudentWithAddressResult">
SELECT STUD_ID, NAME, EMAIL, A.ADDR_ID, STREET, CITY, STATE,
ZIP, COUNTRY
FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON S.ADDR_ID=A.ADDR_ID
WHERE STUD_ID=#{studId}
</select>
我們可以使用圓點(diǎn)記法為內(nèi)嵌的對(duì)象的屬性賦值。在上述的 resultMap 中,Student 的 address 屬性使用了圓點(diǎn)記法 被賦上了 address 對(duì)應(yīng)列的值。同樣地,我們可以訪問任意深度的內(nèi)嵌對(duì)象的屬性。我們可以如下訪問內(nèi)嵌對(duì)象屬性:
/接口定義
public interface StudentMapper
{
Student selectStudentWithAddress(int studId);
}
//使用
int studId = 1;
StudentMapper studentMapper =
sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.selectStudentWithAddress(studId);
System.out.println("Student :" + student);
System.out.println("Address :" + student.getAddress());
上述樣例展示了一對(duì)一關(guān)聯(lián)映射的一種方法。然而,使用這種方式映射,如果 address 結(jié)果需要在其他的 SELECT 映射 語(yǔ)句中映射成 Address 對(duì)象,我們需要為每一個(gè)語(yǔ)句重復(fù)這種映射關(guān)系。MyBatis 提供了更好地實(shí)現(xiàn)一對(duì)一關(guān)聯(lián)映射的方 法:嵌套結(jié)果 ResultMap 和嵌套 select 查詢語(yǔ)句。接下來,我們將討論這兩種方式。
3.4.1 使用嵌套結(jié)果ResultMap實(shí)現(xiàn)一對(duì)一關(guān)系映射
可以使用一個(gè)嵌套結(jié)果 ResultMap 方式來獲取 Student 及其 Address 信息,代碼如下:
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" resultMap="AddressResult" />
</resultMap>
<select id="findStudentWithAddress" parameterType="int"
resultMap="StudentWithAddressResult">
SELECT STUD_ID, NAME, EMAIL, A.ADDR_ID, STREET, CITY, STATE, ZIP, COUNTRY
FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON S.ADDR_ID=A.ADDR_ID
WHERE STUD_ID=#{studId}
</select>
元素association被用來導(dǎo)入“有一個(gè)”(has-one)類型的關(guān)聯(lián)。在上述的例子中,我們使用了association元素 引用了另外的在同一個(gè) XML 文件中定義的resultMap。
也可以使用association 定義內(nèi)聯(lián)的 resultMap,代碼如下所示:
<resultMap type="Student" id="StudentWithAddressResult3">
<id property="studId" column="stud_id"></id>
<result property="name" column="name"></result>
<result property="email" column="email"></result>
<association property="address" javaType="com.mrq.domain.Address">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</association>
</resultMap>
使用嵌套結(jié)果 ResultMap 方式,關(guān)聯(lián)的數(shù)據(jù)可以通過簡(jiǎn)單的查詢語(yǔ)句(如果需要的話,需要與 joins 連接操作配合) 進(jìn)行加載。
3.4.2 使用嵌套查詢實(shí)現(xiàn)一對(duì)一關(guān)系映射
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<select id="findAddressById" parameterType="int"
resultMap="AddressResult">
SELECT * FROM ADDRESSES WHERE ADDR_ID=#{id}
</select>
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" column="addr_id" select="findAddressById" />
</resultMap>
<select id="findStudentWithAddress" parameterType="int"
resultMap="StudentWithAddressResult">
SELECT * FROM STUDENTS WHERE STUD_ID=#{Id}
</select>
在此方式中,<association>元素的 select屬性被設(shè)置成了id為 findAddressById的語(yǔ)句。這里,兩個(gè)分開的 SQL 語(yǔ)句將會(huì)在數(shù)據(jù)庫(kù)中執(zhí)行,第一個(gè)調(diào)用 findStudentById 加載 student 信息,而第二個(gè)調(diào)用 findAddressById 來 加載 address 信息。
Addr_id 列的值將會(huì)被作為輸入?yún)?shù)傳遞給 selectAddressById 語(yǔ)句。 我們可以如下調(diào)用 findStudentWithAddress 映射語(yǔ)句:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectStudentWithAddress(studId);
System.out.println(student);
System.out.println(student.getAddress());
3.5 一對(duì)多映射
在我們的域模型樣例中,一個(gè)講師可以教授一個(gè)或者多個(gè)課程。這意味著講師和課程之間存在一對(duì)多的映射關(guān)系。
我們可以使用<collection>元素將 一對(duì)多類型的結(jié)果 映射到 一個(gè)對(duì)象集合上。
數(shù)據(jù)庫(kù)測(cè)試數(shù)據(jù)
insert into address(`street`,`city`,`state`,`zip`,`country`) values ('8th Street','New York','Wastern','+322','America');
insert into tutors(`name`,email,phone,`addr_id`) values ('John','john@gmail.com','123-456-7890',1);
insert into tutors(`name`,email,phone,`addr_id`) values ('Ying','ying@gmail.com','122-346-7240',2);
insert into courses(`name`,description,start_date,`end_date`,tutor_id) values ('javaSE','Java SE','2014-09-08','2015-09-07',1);
insert into courses(`name`,description,start_date,`end_date`,tutor_id) values ('javaEE','Java EE 8','2014-09-08','2015-09-07',2);
insert into courses(`name`,description,start_date,`end_date`,tutor_id) values ('Mybatis','MyBatis','2014-09-08','2015-09-07',2);
Course 和 Tutor 的 JavaBean 定義如下:
public class Course
{
private Integer courseId;
private String name;
private String description;
private Date startDate;
private Date endDate;
private Integer tutorId;
//setters & getters
}
public class Tutor
{
private Integer tutorId;
private String name;
private String email;
private Address address;
private List<Course> courses;
/ setters & getters
}
現(xiàn)在讓我們看看如何獲取講師信息以及其所教授的課程列表信息。
collection元素被用來將多行課程結(jié)果映射成一個(gè)課程 Course 對(duì)象的一個(gè)集合。和一對(duì)一映射一樣,我們可以使 用嵌套結(jié)果 ResultMap 和嵌套 Select 語(yǔ)句兩種方式映射實(shí)現(xiàn)一對(duì)多映射。
3.5.1 使用內(nèi)嵌結(jié)果ResultMap實(shí)現(xiàn)一對(duì)多映射
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="courseId" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>
<resultMap type="Tutor" id="TutorResult">
<id column="tutor_id" property="tutorId" />
<result column="tutor_name" property="name" />
<result column="email" property="email" />
<collection property="courses" resultMap="CourseResult" />
</resultMap>
<select id="findTutorById" parameterType="int"
resultMap="TutorResult">
SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL, C.COURSE_ID,
C.NAME, DESCRIPTION, START_DATE, END_DATE
FROM TUTORS T LEFT OUTER JOIN ADDRESSES A ON T.ADDR_ID=A.ADDR_ID
LEFT OUTER JOIN COURSES C ON T.TUTOR_ID=C.TUTOR_ID
WHERE T.TUTOR_ID=#{tutorId}
</select>
這里我們使用了一個(gè)簡(jiǎn)單的使用了 JOINS 連接的 Select 語(yǔ)句獲取講師及其所教課程信息。collection元素的 resultMap 屬性設(shè)置成了 CourseResult,CourseResult 包含了 Course 對(duì)象屬性與表列名之間的映射。
3.5.2 使用嵌套 Select 語(yǔ)句實(shí)現(xiàn)一對(duì)多映射
可以使用嵌套 Select 語(yǔ)句方式獲得講師及其課程信息,代碼如下:
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="courseId" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>
<resultMap type="Tutor" id="TutorResult">
<id column="tutor_id" property="tutorId" />
<result column="tutor_name" property="name" />
<result column="email" property="email" />
<association property="address" resultMap="AddressResult" />
<collection property="courses" column="tutor_id" select="findCoursesByTutor" />
</resultMap>
<select id="findTutorById" parameterType="int" resultMap="TutorResult">
SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL
FROM TUTORS T WHERE T.TUTOR_ID=#{tutorId}
</select>
<select id="findCoursesByTutor" parameterType="int" resultMap="CourseResult">
SELECT * FROM COURSES WHERE TUTOR_ID=#{tutorId}
</select>
在這種方式中,<aossication>元素的 select 屬性被設(shè)置為 id 為 findCourseByTutor 的語(yǔ)句,用來觸發(fā)單獨(dú) 的 SQL 查詢加載課程信息。tutor_id 這一列值將會(huì)作為輸入?yún)?shù)傳遞給 findCouresByTutor 語(yǔ)句。
public interface TutorMapper
{
Tutor findTutorById(int tutorId);
}
TutorMapper mapper = sqlSession.getMapper(TutorMapper.class);
Tutor tutor = mapper.findTutorById(tutorId);
System.out.println(tutor);
List<Course> courses = tutor.getCourses();
for (Course course : courses)
{
System.out.println(course);
}
嵌套 Select 語(yǔ)句查詢會(huì)導(dǎo)致 N+1 選擇問題。首先,主查詢將會(huì)執(zhí)行(1 次),對(duì)于主 查詢返回的每一行,另外一個(gè)查詢將會(huì)將會(huì)被執(zhí)行(主查詢 N 行,則此查詢 N 次)。對(duì)于 大型數(shù)據(jù)庫(kù)而言,這會(huì)導(dǎo)致很差的性能問題。
來了就領(lǐng)一個(gè)紅包再走吧.
