Java數(shù)據(jù)持久化之mybatis
一. mybatis簡介
mybatis是一個簡化和實現(xiàn)了Java數(shù)據(jù)持久層的開源框架,它抽象了大量的JDBC冗余代碼,并且提供簡單易用的api和數(shù)據(jù)庫交互.mybatis的前身是ibatis,ibatis由Clinton Begin 創(chuàng)建.mybatis3是ibatis的全新設計,支持注解和mapper.
MyBatis 流行的主要原因在于它的簡單性和易使用性撼嗓。在 Java 應用程序中哑梳,數(shù)據(jù)持久化層涉及到的工作有:將從數(shù) 據(jù)庫查詢到的數(shù)據(jù)生成所需要的 Java 對象;將 Java 對象中的數(shù)據(jù)通過 SQL 持久化到數(shù)據(jù)庫中巩梢。
MyBatis 通過抽象底層的JDBC代碼,自動化SQL結果集產(chǎn)生Java對象萝勤、Java對象的數(shù)據(jù)持久化數(shù)據(jù)庫中的過程 使得對 SQL 的使用變得容易。
1.1 原始的JDBC操作: Java 通過 Java 數(shù)據(jù)庫連接(Java DataBase Connectivity,JDBC)API 來操作關系型數(shù)據(jù)庫,但是 JDBC 是一個
非常底層的 API割择,我們需要書寫大量的代碼來完成對數(shù)據(jù)庫的操作立宜。
讓我們演示一下我們是怎樣使用純的 JDBC 來對表 STUDENTS 實現(xiàn)簡單的 select 和 insert 操作的。
假設表 STUDENTS 有 STUD_ID臊岸,NAME橙数,EMAIL 和 DOB 字段。對應的 Student JavaBean 定義如下:
public class Student {
private Integer studId;
private String name;
private String email;
private Date dob;
//setter and getter
}
下面的StudentService實現(xiàn)了通過JDBC對表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);
}
}
上面的每一步操作都有大量的重復代碼:創(chuàng)建一個連接,創(chuàng)建一個statement對象,設置參數(shù),關閉資源. mybatis抽象了上述的這些相同的任務:如準備需要被執(zhí)行的SQL statement對象并且將Java對象作為輸入數(shù)據(jù) 傳遞給 statement 對象的任務帅戒,進而開發(fā)人員可以專注于真正重要的方面灯帮。
另外,MyBatis 自動化了將從輸入的 Java 對象中的屬性設置成查詢參數(shù)逻住、從 SQL 結果集上生成 Java 對象這兩個過 程钟哥。
現(xiàn)在讓我們看看怎樣通過 MyBatis 實現(xiàn)上述的方法:
- 在 SQL Mapper映射配置文件中配置SQL語句,假定文件為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)建一個StudentMapper接口
public interface StudentMapper {
Student findStudentById(Integer id);
void insertStudent(Student student);
}
- 在Java代碼操作中,可以使用以下代碼方式觸發(fā)SQL語句
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操作簡單了許多:不需要創(chuàng)建Connection和PreparedStatement,不需要自己對每一次數(shù)據(jù)庫操作進行手動設 置參數(shù)和關閉連接。只需要配置數(shù)據(jù)庫連接屬性和 SQL 語句瞎访,
MyBatis 會處理這些底層工作腻贰。
另外,MyBatis 還提供了其他的一些特性來簡化持久化邏輯的實現(xiàn):
- 它支持復雜的SQL結果集數(shù)據(jù)映射到嵌套對象圖結構
- 它支持一對一和一對多的結果集和Java對象的映射
- 它支持根據(jù)輸入的數(shù)據(jù)構建動態(tài)的SQL語句
1.2 mybatis的安裝和配置
1.2.1 新建一個maven項目,在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表,插入測試數(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ù)庫連接信息扒秸,類型別名等等;StudentMapper.xml包含了映射的 SQL 語句
- 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 語句可以通過 ID 加上名空間調用播演。
1.2.4 新建 MyBatisSqlSessionFactory 單例類 使其持有一個 SqlSessionFactory 單例對象:
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對象讓當前線程與SqlSession對象綁定在一起
3)獲取當前線程中的SqlSession對象,如果沒有的話伴奥,從SqlSessionFactory對象中獲取SqlSession對象
4)獲取當前線程中的SqlSession對象写烤,再將其關閉,釋放其占用的資源
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語句
Student student = (Student)sqlSession.
selectOne("com.mrq.mappers.StudentMapper.findStudentById",
studId);
1.2.6 創(chuàng)建測試Service類測試
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 語句的映射。
我們使用 mybatis-config.xml 內的信息創(chuàng)建了 SqlSessionFactory 對象尼啡。每個數(shù)據(jù)庫環(huán)境應該就一個
SqlSessionFactory 對象實例暂衡,所以我們使用了單例模式只創(chuàng)建一個 SqlSessionFactory 實例。
我們創(chuàng)建了一個映射器 Mapper 接口-StudentMapper玄叠,其定義的方法簽名和在 StudentMapper.xml 中定義的完全 一樣(即映射器 Mapper 接口中的方法名跟 StudentMapper.xml 中的 id 的值相同)古徒。注意 StudentMapper.xml 中 namespace 的值被設置成 com.mrq.mappers.StudentMapper,是 StudentMapper 接口的完全限定名读恃。這使我們 可以使用接口來調用映射的 SQL 語句隧膘。
在 StudentService.java 中,我們在每一個方法中創(chuàng)建了一個新的 SqlSession寺惫,并在方法功能完成后關閉 SqlSession疹吃。每一個線程應該有它自己的 SqlSession 實例。SqlSession 對象實例不是線程安全的西雀,并且不被共享萨驶。所 以 SqlSession 的作用域最好就是其所在方法的作用域。從 Web 應用程序角度上看艇肴,SqlSession 應該存在于 request 級 別作用域上腔呜。
二. mybatis配置主要內容
MyBatis 最關鍵的組成部分是 SqlSessionFactory叁温,我們可以從中獲取 SqlSession,并執(zhí)行映射的 SQL 語句核畴。
SqlSessionFactory 對象可以通過基于 XML 的配置信息或者 Java API 創(chuàng)建膝但。
我們將探索各種 MaBatis 配置元素,如 dataSource谤草,environments,全局參數(shù)設置,typeAlias跟束,typeHandlers,
SQL 映射;接著我們將實例化 SqlSessionFactory丑孩。
2.1 使用 XML 配置 MyBatis
構建 SqlSessionFactory 最常見的方式是基于 XML 配置(的構造方式)冀宴。下面的 mybatis-config.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>
<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>
下面讓我們逐個討論上述配置文件的組成部分,先從最重要的部分開始温学,即 environments:
2.1.1 environment
MyBatis 支持配置多個 dataSource 環(huán)境略贮,可以將應用部署到不同的環(huán)境上,如 DEV(開發(fā)環(huán)境)枫浙,TEST(測試換將)刨肃, QA(質量評估環(huán)境),UAT(用戶驗收環(huán)境),PRODUCTION(生產(chǎn)環(huán)境),可以通過將默認 environment 值設置成想要的 environment id 值箩帚。
在上述的配置中真友,默認的環(huán)境 environment 被設置成 development。當需要將程序部署到生產(chǎn)服務器上時紧帕,你不需 要修改什么配置盔然,只需要將默認環(huán)境environment值設置成生產(chǎn)環(huán)境的 environmentid屬性即可。
有時候是嗜,我們可能需要在相同的應用下使用多個數(shù)據(jù)庫愈案。比如我們可能有 SHOPPING-CART 數(shù)據(jù)庫來存儲所有的訂單 明細;使用 REPORTS 數(shù)據(jù)庫存儲訂單明細的合計,用作報告鹅搪。
如果你的應用需要連接多個數(shù)據(jù)庫站绪,你需要將每個數(shù)據(jù)庫配置成獨立的環(huán)境,并且為每一個數(shù)據(jù)庫創(chuàng)建一個 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>
我們可以如下為每個環(huán)境創(chuàng)建一個 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 時恢准,如果沒有明確指定環(huán)境 environment id,則會使用默認的環(huán)境 environment 來創(chuàng) 建甫题。在上述的源碼中馁筐,默認的 SqlSessionFactory 便是使用 shoppingcart 環(huán)境設置創(chuàng)建的。
對于每個環(huán)境 environment,我們需要配置 dataSource 和 transactionManager 元素坠非。
2.1.2 數(shù)據(jù)源DataSource
dataSource 元素被用來配置數(shù)據(jù)庫連接屬性敏沉。
<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>
- 使用連接池
<!--定義一個jdbc數(shù)據(jù)源,創(chuà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 的類型可以配置成其內置類型之一,如 UNPOOLED盟迟,POOLED秋泳,JNDI。
如果將類型設置成UNPOOLED攒菠,MyBatis會為每一個數(shù)據(jù)庫操作創(chuàng)建一個新的連接轮锥,并關閉它。該方式適用于只有小規(guī)模數(shù)量并發(fā)用戶的簡單應用程序上要尔。
如果將屬性設置成POOLED,MyBatis會創(chuàng)建一個數(shù)據(jù)庫連接池新娜,連接池中的一個連接將會被用作數(shù)據(jù)庫操作赵辕。一旦數(shù)據(jù)庫操作完成,MyBatis 會將此連接返回給連接池概龄。在開發(fā)或測試環(huán)境中还惠,經(jīng)常使用此
種方式。如果將類型設置成JNDI私杜,MyBatis從在應用服務器向配置好的JNDI數(shù)據(jù)源dataSource獲取數(shù)據(jù)庫連接蚕键。在生產(chǎn)環(huán)境中,優(yōu)先考慮這種方式衰粹。
2.1.3 事務管理器 TransactionManager
MyBatis 支持兩種類型的事務管理器: JDBC and MANAGED.
JDBC事務管理器被用作當應用程序負責管理數(shù)據(jù)庫連接的生命周期(提交锣光、回退等等)的時候。當你將TransactionManager 屬性設置成 JDBC铝耻,MyBatis 內部將使用 JdbcTransactionFactory 類創(chuàng)建TransactionManager誊爹。例如,部署到 Apache Tomcat 的應用程序瓢捉,需要應用程序自己管理事務频丘。
MANAGED 事務管理器是當由應用服務器負責管理數(shù)據(jù)庫連接生命周期的時候使用。當你將 TransactionManager 屬性設置成 MANAGED 時泡态,MyBatis 內部使用 ManagedTransactionFactory 類
創(chuàng)建事務管理器TransactionManager搂漠。例如,當一個JavaEE的應用程序部署在類似 JBoss某弦,WebLogic桐汤, GlassFish 應用服務器上時,它們會使用 EJB 進行應用服務器的事務管理能力刀崖。在這些管理環(huán)境中惊科,你 可以使用 MANAGED 事務管理器。
(譯者注:Managed 是托管的意思亮钦,即是應用本身不去管理事務馆截,而是把事務管理交給應用所在的服務 器進行管理。)
2.1.4 屬性properties
屬性配置元素可以將配置值具體化到一個屬性文件中,并且使用屬性文件的 key 名作為占位符蜡娶。在上述的配置中混卵,我 們將數(shù)據(jù)庫連接屬性具體化到了 application.properties 文件中,并且為 driver窖张,URL 等屬性使用了占位符幕随。
- 在 applications.properties 文件中配置數(shù)據(jù)庫連接參數(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>元素中配置默認參數(shù)的值。如果<properties>中定義的元素和屬性文件定義元素的 key 值相同睦霎,它們會被屬性文件中定義的值覆蓋梢卸。
這里,如果 application.properties 文件包含值 jdbc.username 和 jdbc.password副女,則上述定義的 username 和 password 的值 db_user 和 verysecurepwd 將會被 application.properties 中定義的對應的 jdbc.username 和 jdbc.password 值覆蓋蛤高。
2.1.5 類型別名 typeAliases:
在 SQLMapper 配置文件中,對于 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 屬性值設置為 Student 類型的完全限定名: com.mrq.domain.Student
我們可以為完全限定名取一個別名(alias),然后其需要使用完全限定名的地方使用別名沟涨,而不是到處使用完全限定 名恤批。如下例子所示,為完全限定名起一個別名.然后在 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>
我們可以不用為每一個JavaBean單獨定義別名, 你可以為提供需要取別名的JavaBean所在的包(package)裹赴,MyBatis 會自動掃描包內定義的 JavaBeans开皿,然后分別為 JavaBean 注冊一個小寫字母開頭的非完全限定的類名形式的別名。如下所 示篮昧,提供一個需要為 JavaBeans 起別名的包名:
<typeAliases>
<package name="com.mrq.domain"/>
</typeAliases>
如果 Student.java 和 Tutor.java Bean 定義在 com.mrq.domain 包中赋荆,則 com.mrq.domain.Student 的別名會被注冊為 student。而 com.mrq.domain.Tutor 別名將會被注冊為 tutor.
還有另外一種方式為 JavaBeans 起別名懊昨,使用注解@Alias:
@Alias("StudentAlias")
public class Student
{
}
@Alias 注解將會覆蓋配置文件中的<typeAliases>定義窄潭。
2.1.6 類型處理器 typeHandlers
如上一章已經(jīng)討論過,MyBatis 通過抽象 JDBC 來簡化了數(shù)據(jù)持久化邏輯的實現(xiàn)酵颁。MyBatis 在其內部使用 JDBC嫉你,提供了更簡潔的方式實現(xiàn)了數(shù)據(jù)庫操作。
當 MyBatis 將一個 Java 對象作為輸入?yún)?shù)執(zhí)行 INSERT 語句操作時躏惋,它會創(chuàng)建一個 PreparedStatement 對象幽污,并且 使用 setXXX()方式對占位符設置相應的參數(shù)值。
這里簿姨,XXX 可以是 Int距误,String簸搞,Date 等 Java 對象屬性類型的任意一個。示例如下:
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB)
VALUES(#{studId},#{name},#{email},#{dob})
</insert>
為執(zhí)行這個SQL語句,mybatis將會執(zhí)行下面的步驟
- 創(chuàng)建一個有占位符的PreparedStatement 接口,如下:
PreparedStatement pstmt = connection.prepareStatement("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB) VALUES(?,?,?,?)");
- 檢查Student對象的屬性studId的類型,然后使用合適setXXX方法去設置參數(shù)值警没。這里studId是integer 類型,所以會使用 setInt()方法:
pstmt.setInt(1,student.getStudId());
- 類似地寺擂,對于name和email屬性都是String類型,MyBatis使用setString()方法設置參數(shù)泼掠。
pstmt.setString(2, student.getName());
pstmt.setString(3, student.getEmail());
4.對于dob屬性,MyBatis會使用setDate()方法設置dob處占位符位置的值怔软。
5.MyBaits 會將 java.util.Date 類型轉換為 java.sql.Timestamp 并設值:
但 MyBatis 是怎么知道對于 Integer 類型屬性使用 setInt() 和 String 類型屬性使用 setString()方法呢? 其實 MyBatis 是通過使用類型處理器(type handlers)來決定這么做的。
MyBatis 對于以下的類型使用內建的類型處理器:所有的基本數(shù)據(jù)類型择镇、基本類型的包裹類型爽雄、byte[]、 java.util.Date沐鼠、java.sql.Date、java,sql.Time叹谁、java.sql.Timestamp饲梭、java 枚舉類型等。所以當 MyBatis 發(fā)現(xiàn) 屬性的類型屬于上述類型焰檩,他會使用對應的類型處理器將值設置到 PreparedStatement 中憔涉,同樣地,當從 SQL 結果集構 建 JavaBean 時析苫,也有類似的過程.
那如果我們給了一個自定義的對象類型兜叨,來存儲存儲到數(shù)據(jù)庫呢?示例如下:
假設表STUDENTS有一個PHONE字段,類型為VARCHAR(15)衩侥,而JavaBean Student有一個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 對象是 PhoneNumber 類型。然而茫死,MyBatis 并不知道該怎樣來處 理這個類型的對象跪但。
為了讓 MyBatis 明白怎樣處理這個自定義的 Java 對象類型,如 PhoneNumber峦萎,我們可以創(chuàng)建一個自定義的類型處理 器屡久,如下所示:
- 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()方法是因為phone列是VARCHAR類型被环。
3.一旦我們實現(xiàn)了自定義的類型處理器,我們需要在mybatis-config.xml中注冊它:
<typeHandlers>
<typeHandler handler="com.mrq.typehandlers.PhoneTypeHandler"/>
<package name="com.mrq.typehandlers"/>
</typeHandlers>
注冊 PhoneTypeHandler 后, MyBatis 就能夠將 Phone 類型的對象值存儲到 VARCHAR 類型的列上详幽。
2.1.7 全局參數(shù)設置 Settings
為滿足應用特定的需求筛欢,MyBatis 默認的全局參數(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 映射語句將會被應用通過使用其 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>
以上每一個<mapper> 標簽的屬性有助于從不同類型的資源中加載映射 mapper:
- resource屬性用來指定在classpath中的mapper文件揣钦。
- url屬性用來通過完全文件系統(tǒng)路徑或者webURL地址來指向mapper文件
- class屬性用來指向一個mapper接口
- package屬性用來指向可以找到Mapper接口的包名
三. 使用 XML 配置 SQL 映射器
關系型數(shù)據(jù)庫和SQL是經(jīng)受時間考驗和驗證的數(shù)據(jù)存儲機制。和其他的ORM 框架如Hibernate不同漠酿,MyBatis鼓勵 開發(fā)者可以直接使用數(shù)據(jù)庫冯凹,而不是將其對開發(fā)者隱藏,因為這樣可以充分發(fā)揮數(shù)據(jù)庫服務器所提供的 SQL 語句的巨大威 力炒嘲。與此同時浑劳,MyBaits 消除了書寫大量冗余代碼的痛苦夭拌,它使使用 SQL 更容易。
在代碼里直接嵌套 SQL 語句是很差的編碼實踐鸽扁,并且維護起來困難相赁。MyBaits 使用了映射器配置文件或注解來配置 SQL 語句跺嗽。下面废境,我們會看到具體怎樣使用映射器配置文件來配置映射 SQL 語句。
3.1 映射配置文件和映射接口
在前幾章中,我們已經(jīng)看見了一些在映射器配置文件中配置基本的映射語句,以及怎樣使用 SqlSession 對象調用它們 的例子映跟。
現(xiàn)在讓我們看一下在 com.mrq.mappers 包中的 StudentMapper.xml 配置文件內,是如何配置 id 為” findStudentById”的 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.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>
我們可以通過下列代碼調用 findStudentById 映射的 SQL 語句:
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 接口提供了更好的調用映射語句的方法。一旦我們通過映射器配置文件配置了映射語 句,我們可以創(chuàng)建一個完全對應的一個映射器接口,接口名跟配置文件名相同诸狭,接口所在包名也跟配置文件所在包名完全 一 樣(如 StudentMapper.xml 所 在 的 包 名 是 com.mrq.mappers, 對 應 的 接 口 名 就 是com.mrq.mappers.StudentMapper.java )。映射器接口中的方法簽名也跟映射器配置文件中完全對應:方法名為 配置文件中 id 值;方法參數(shù)類型為 parameterType 對應值;方法返回值類型為 returnType 對應值讳癌。
對于上述的 StudentMapper.xml 文件搔课,我們可以創(chuàng)建一個映射器接口 StudentMapper.java 如下:
package com.mrq.mappers;
public interface StudentMapper
{
Student findStudentById(Integer id);
}
在StudentMapper.xml 映射器配置文件中胰柑,其名空間namespace 應該跟StudentMapper接口的完全限定名保持一 致。另外爬泥,StudentMapper.xml 中語句 id柬讨,parameterType,returnType 應該分別和 StudentMapper 接口中的方法名袍啡, 參數(shù)類型踩官,返回值相對應.
使用映射器接口我們可以以類型安全的形式調用調用映射語句。如下所示:
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 接口可以以類型安全的方式調用映射語句境输,但是我們我 負責書寫正確的颖系,匹配方法名趁啸、參數(shù)類型、和返回值的映射器 Mapper 接口留拾。 如果映射器 Mapper 接口中的方法和 XML 中的映射語句不能匹配豪嚎,會在運行期 拋出一個異常温技。實際上博其,指定 parameterType 是可選的;MyBatis 可以使用反 射機制來決定 parameterType以清。但是,從配置可讀性的角度來看呀潭,最好指定 parameterType 屬性狸棍。如果 parameterType 沒有被提及丢习,開發(fā)者必須查看 Mapper XML 配置和 Java 代碼了解傳遞給語句的輸入?yún)?shù)的數(shù)據(jù)類
3.2 映射語句
MyBatis 提供了多種元素來配置不同類型的語句,如 SELECT鲤屡,INSERT损痰,UPDATE,DELETE酒来。接下來讓我們看看如何具體配置映射語句.
3.2.1 insert 語句:一個 INSERT SQL 語句可以在<insert>元素在映射器 XML 配置文件中配置卢未,如下所示:
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE)
VALUES(#{studId},#{name},#{email},#{phone})
</insert>
這里我們使用一個ID insertStudent,可以在名空間com.mrq.mappers.StudentMapper.insertStudent中 唯一標識堰汉。parameterType 屬性應該是一個完全限定類名或者是一個類型別名(alias)辽社。
我們可以如下調用這個語句:
int count =
sqlSession.insert("com.mrq.mappers.StudentMapper.insertStudent", student);
sqlSession.insert() 方法返回執(zhí)行 INSERT 語句后所影響的行數(shù)。
如果不使用名空間(namespace)和語句 id 來調用映射語句翘鸭,你可以通過創(chuàng)建一個映射器 Mapper 接口滴铅,并以類型安 全的方式調用方法,如下所示:
package com.mrq.mappers;
public interface StudentMapper
{
int insertStudent(Student student);
}
可以如下調用 insertStudent 映射語句:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int count = mapper.insertStudent(student);
- 自動生成主鍵
在上述的INSERT語句中就乓,我們?yōu)榭梢宰詣由?auto-generated)主鍵的列 STUD_ID插入值失息。我們可以使用 useGeneratedKeys 和 keyProperty屬性讓數(shù)據(jù)庫生成 auto_increment列的值,并將生成的值設置到其中一個 輸入對象屬性內档址,如下所示:
<insert id="insertStudent" parameterType="Student" useGeneratedKeys="true"
keyProperty="studId">
INSERT INTO STUDENTS(NAME, EMAIL, PHONE) VALUES(#{name},#{email},#{phone})
</insert>
這里 STUD_ID 列值將會被 MySQL 數(shù)據(jù)庫自動生成盹兢,并且生成的值會被設置到 student 對象的 studId 屬性上。
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
mapper.insertStudent(student);
現(xiàn)在可以如下獲取插入的 STUDENT 記錄的 STUD_ID 的值:
int studentId = student.getStudId();
有些數(shù)據(jù)庫如 Oracle 并不支持 AUTO_INCREMENT 列守伸,其使用序列(SEQUENCE)來生成主鍵值绎秒。 假設我們有一個名為 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 對象的 studId 屬性上见芹。 屬性 order=“before”表示 MyBatis 將取得序列的下一個值作為主鍵值,并且在執(zhí)行 INSERT SQL 語句之前將值設置到 studId 屬性上蠢涝。
我們也可以在獲取序列的下一個值時玄呛,使用觸發(fā)器(trigger)來設置主鍵值,并且在執(zhí)行INSERT SQL語句之 前將值設置到主鍵列上和二。如果你采取這樣的方式徘铝,則對應的 INSERT 映射語句如下所示:
<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 語句
一個 UPDATE SQL 語句可以在<update>元素在映射器 XML 配置文件中配置,如下所示:
<update id="updateStudent" parameterType="Student">
UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email}, PHONE=#{phone}
WHERE STUD_ID=#{studId}
</update>
可以如下調用此語句:
int noOfRowsUpdated =
sqlSession.update("com.mrq.mappers.StudentMapper.updateStudent", student);
sqlSession.update() 方法返回執(zhí)行 UPDATE 語句之后影響的行數(shù)。
如果不使用名空間(namespace)和語句 id 來調用映射語句惕它,你可以通過創(chuàng)建一個映射器 Mapper 接口怕午,并以類型 安全的方式調用方法,如下所示:
public interface StudentMapper
{
int updateStudent(Student student);
}
可以使用映射器 Mapper 接口來調用 updateStudent 語句淹魄,如下所示:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int noOfRowsUpdated = mapper.updateStudent(student);
3.2.3 刪除語句
一個 DELETE SQL 語句可以在<delete>元素在映射器 XML 配置文件中配置郁惜,如下所示:
<delete id="deleteStudent" parameterType="int">
DELETE FROM STUDENTS WHERE STUD_ID=#{studId}
</delete>
可以如下調用此語句:
int studId = 1;
int noOfRowsDeleted =
sqlSession.delete("com.mrq.mappers.StudentMapper.deleteStudent", studId);
sqlSession.delete() 方法返回 delete 語句執(zhí)行后影響的行數(shù)。
如果不使用名空間(namespace)和語句 id 來調用映射語句甲锡,你可以通過創(chuàng)建一個映射器 Mapper 接口兆蕉,并以類型安 全的方式調用方法,如下所示:
public interface StudentMapper
{
int deleteStudent(int studId);
}
可以使用映射器 Mapper 接口來調用 deleteStudent 語句缤沦,如下所示:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int noOfRowsDeleted = mapper.deleteStudent(studId);
3.2.4 select 語句
MyBatis 真正強大的功能恨樟,在于映射 SELECT 查詢結果到 JavaBeans 方面的極大靈活性。 讓我們看看一個簡單的 select 查詢是如何(在 MyBatis 中)配置的疚俱,如下所示:
<select id="findStudentById" parameterType="int"
resultType="Student">
SELECT STUD_ID, NAME, EMAIL, PHONE
FROM STUDENTS
WHERE STUD_ID=#{studId}
</select>
可以如下調用此語句:
int studId =1;
Student student = sqlSession.selectOne("com.mrq.mappers.
StudentMapper.findStudentById", studId);
不使用名空間(namespace)和語句 id 來調用映射語句劝术,你可以通過創(chuàng)建一個映射器 Mapper 接口,并以類型安 全的方式調用方法呆奕,如下所示:
public interface StudentMapper
{
Student findStudentById(Integer studId);
}
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.findStudentById(studId);
如果你檢查 Student 對象的屬性值养晋,你會發(fā)現(xiàn) studId 屬性值并沒有被 stud_id 列值填充。這是因為 MyBatis 自動對 JavaBean 中和列名匹配的屬性進行填充梁钾。這就是為什么 name ,email,和 phone 屬性被填充绳泉,而 studId 屬性沒有被填 充。
為了解決這一問題姆泻,我們可以為列名起一個可以與 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 這個 Bean 對象中的值將會恰當?shù)乇?stud_id,name,email,phone 列填充了拇勃。 現(xiàn)在四苇,讓我們看一下如何執(zhí)行返回多條結果的 SELECT 語句查詢,如下所示:
<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();
}
可以如下調用 findAllStudents 語句:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.findAllStudents();
如果你注意到上述的 SELECT 映射定義方咆,你可以看到月腋,我們?yōu)樗械挠成湔Z句中的 stud_id 起了別名。我們可以使用 ResultMaps瓣赂,來避免上述的到處重復別名榆骚。我們稍后會繼續(xù)討論。
除了 java.util.List煌集,你也可以是由其他類型的集合類妓肢,如 Set,Map,以及(SortedSet)苫纤。MyBatis 根據(jù)集合的類型碉钠,會采用適當?shù)募蠈崿F(xiàn)纲缓,如下所示:
- 對于 List,Collection放钦,Iterable 類型,MyBatis 將返回 java.util.ArrayList
- 對于 Map 類型恭金,MyBatis 將返回 java.util.HashMap
- 對于 Set 類型操禀,MyBatis 將返回 java.util.HashSet
- 對于 SortedSet 類型,MyBatis 將返回 java.util.TreeSet
3.3 結果集映射 ResultMaps
ResultMaps被用來 將SQLSELECT語句的結果集映射到 JavaBeans的屬性中横腿。我們可以定義結果集映射ResultMaps 并且在一些 SELECT 語句上引用 resultMap颓屑。MyBatis 的結果集映射 ResultMaps 特性非常強大,你可以使用它將簡單的 SELECT 語句映射到復雜的一對一和一對多關系的 SELECT 語句上耿焊。
3.3.1 簡單的resultMap
映射了查詢結果和 Student JavaBean 的簡單的 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值應該在此名空間內是唯一的揪惦。并且type屬性應該是完全限定類名或者是返 回類型的別名。
<result>子元素被用來將一個 resultset 列映射到 JavaBean 的一個屬性中罗侯。
<id>元素和<result>元素功能相同器腋,不過它被用來映射到唯一標識屬性,用來區(qū)分和比較對象(一般和主鍵列相對應)钩杰。
在<select>語句中纫塌,我們使用了 resultMap 屬性,而不是 resultType 來引用 StudentResult 映射讲弄。當<select>語 句中配置了 resutlMap 屬性措左,MyBatis 會使用此數(shù)據(jù)庫列名與對象屬性映射關系來填充 JavaBean 中的屬性。
* resultType 和 resultMap 二者只能用其一避除,不能同時使用怎披。
看另外一個select映射語句定義的例子,怎樣將查詢結果填充到 HashMap 中瓶摆。如下所示:
<select id="findStudentById" parameterType="int" resultType="map">
SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}
</select>
在上述的select語句中凉逛,我們將 resultType 配置成 map,即 java.util.HashMap 的別名群井。在這種情況下鱼炒,結果集 的列名將會作為 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"));
再看一個 使用resultType=”map”,返回多行結果的例子:
<select id="findAllStudents" resultType="map">
SELECT STUD_ID, NAME, EMAIL, PHONE FROM STUDENTS
</select>
由于 resultType=”map”和語句返回多行昔瞧,則最終返回的數(shù)據(jù)類型應該是 "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
我們可以從從另外一個<resultMap>菩佑,拓展出一個新的<resultMap>自晰,這樣,原先的屬性映射可以繼承過來稍坯,以實現(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 一對一映射
在我們的域模型樣例中枪向,每一個學生都有一個與之關聯(lián)的地址信息。表 STUDENTS 有一個 ADDR_ID 列咧党,是 ADDRESSES 表的外鍵秘蛔。
看一下怎樣取 Student 明細和其 Address 明細。
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>
我們可以使用圓點記法為內嵌的對象的屬性賦值傍衡。在上述的 resultMap 中深员,Student 的 address 屬性使用了圓點記法 被賦上了 address 對應列的值。同樣地蛙埂,我們可以訪問任意深度的內嵌對象的屬性倦畅。我們可以如下訪問內嵌對象屬性:
/接口定義
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());
上述樣例展示了一對一關聯(lián)映射的一種方法。然而绣的,使用這種方式映射叠赐,如果 address 結果需要在其他的 SELECT 映射 語句中映射成 Address 對象,我們需要為每一個語句重復這種映射關系屡江。MyBatis 提供了更好地實現(xiàn)一對一關聯(lián)映射的方 法:嵌套結果 ResultMap 和嵌套 select 查詢語句燎悍。接下來,我們將討論這兩種方式盼理。
3.4.1 使用嵌套結果ResultMap實現(xiàn)一對一關系映射
可以使用一個嵌套結果 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被用來導入“有一個”(has-one)類型的關聯(lián)。在上述的例子中宏怔,我們使用了association元素 引用了另外的在同一個 XML 文件中定義的resultMap奏路。
也可以使用association 定義內聯(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>
使用嵌套結果 ResultMap 方式臊诊,關聯(lián)的數(shù)據(jù)可以通過簡單的查詢語句(如果需要的話鸽粉,需要與 joins 連接操作配合) 進行加載。
3.4.2 使用嵌套查詢實現(xià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屬性被設置成了id為 findAddressById的語句触机。這里,兩個分開的 SQL 語句將會在數(shù)據(jù)庫中執(zhí)行玷或,第一個調用 findStudentById 加載 student 信息儡首,而第二個調用 findAddressById 來 加載 address 信息。
Addr_id 列的值將會被作為輸入?yún)?shù)傳遞給 selectAddressById 語句偏友。 我們可以如下調用 findStudentWithAddress 映射語句:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectStudentWithAddress(studId);
System.out.println(student);
System.out.println(student.getAddress());
3.5 一對多映射
在我們的域模型樣例中蔬胯,一個講師可以教授一個或者多個課程。這意味著講師和課程之間存在一對多的映射關系位他。
我們可以使用<collection>元素將 一對多類型的結果 映射到 一個對象集合上氛濒。
數(shù)據(jù)庫測試數(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元素被用來將多行課程結果映射成一個課程 Course 對象的一個集合。和一對一映射一樣舞竿,我們可以使 用嵌套結果 ResultMap 和嵌套 Select 語句兩種方式映射實現(xiàn)一對多映射京景。
3.5.1 使用內嵌結果ResultMap實現(xiàn)一對多映射
<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>
這里我們使用了一個簡單的使用了 JOINS 連接的 Select 語句獲取講師及其所教課程信息。collection元素的 resultMap 屬性設置成了 CourseResult骗奖,CourseResult 包含了 Course 對象屬性與表列名之間的映射确徙。
3.5.2 使用嵌套 Select 語句實現(xiàn)一對多映射
可以使用嵌套 Select 語句方式獲得講師及其課程信息,代碼如下:
<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 屬性被設置為 id 為 findCourseByTutor 的語句米愿,用來觸發(fā)單獨 的 SQL 查詢加載課程信息厦凤。tutor_id 這一列值將會作為輸入?yún)?shù)傳遞給 findCouresByTutor 語句鼻吮。
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 語句查詢會導致 N+1 選擇問題。首先较鼓,主查詢將會執(zhí)行(1 次)椎木,對于主 查詢返回的每一行,另外一個查詢將會將會被執(zhí)行(主查詢 N 行博烂,則此查詢 N 次)香椎。對于 大型數(shù)據(jù)庫而言,這會導致很差的性能問題禽篱。
來了就領一個紅包再走吧.