前言
利用端午時間簡單學習了MyBatis,現根據學習內容進行簡單總結。
知識點
1. MyBatis框架簡介
MyBatis是一個持久化框架,使用Java編寫,封裝了JDBC的很多實現細節,可以使開發者只關注SQL語句,而無需關注註冊驅動/建立連接等繁雜的過程,封裝過程使用ORM思想來實現。
ORM:Object Relational Mapping 對象關係映射,把數據庫表和實體類的屬性對應起來,使得可以通過操作實體類來實現數據庫表
2. 基於XML和基於註解的開發實例
以User爲實體類,構建簡單的一個入門實例,通過MyBatis操作實體類完成對數據庫user表的CRUD操作,之後分別會用基於XML和基於註解的方式完成。
2.1 建表
通過Navicat連接MySQL完成建表操作。
2.1.1 啓動mysql服務【管理員模式打開cmd】
2.1.2 創建User表並插入數據
2.2 導入依賴
創建maven項目,導入mybatis/mysql-connection等依賴
<dependencies>
<!-- MyBatis依賴 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- mysql依賴 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- log4j依賴 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
<!-- 單元測試依賴 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
2.3 創建User實體類/IUserDao接口
2.3.1 根據數據庫字段創建User實體類並定義屬性和getter/setter方法
/**
* User實體類
*/
public class User implements Serializable {
private Long userId;
private String username;
private String password;
private Date birthday;
private String sex;
private String address;
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", username='" + username + '\'' +
", password='" + password + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
2.3.1 創建User接口類,並根據查詢所有用戶功能創建抽象方法
public interface IUserDao {
/**
* 查詢所有用戶
* @return
*/
List<User> findAllUsers(); //public abstract已省略
}
2.4 添加映射配置文件
配置文件主要是配置數據庫連接信息以及爲方便給類名起別名,配置映射文件的路徑
<?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">
<!--MyBatis的主配置文件-->
<configuration>
<!--配置環境-->
<environments default="mysql">
<!--配置mysql環境-->
<environment id="mysql">
<!--配置事務的類型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置數據源/連接池-->
<dataSource type="POOLED">
<!--配置連接數據庫的4個基本信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,映射配置文件指的是每個dao獨立的配置文件-->
<mappers>
<mapper resource="com/practice/dao/IUserDao.xml"/>
</mappers>
</configuration>
2.5 基於XML模式的映射文件及查詢所有用戶功能測試
基於XML模式主要是映射配置文件中mapper中會指定resource爲映射文件的全限定路徑,另外根據IUserDao接口的方法編寫映射文件。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace鎖定接口 -->
<mapper namespace="com.practice.dao.IUserDao">
<!-- id鎖定方法,resultType爲方法返回類型,另外還有parameterType表示方法參數類型 -->
<select id="findAllUsers" resultType="com.practice.domain.User">
select * from user
</select>
</mapper>
public class IUserTest {
private InputStream in;
private SqlSession session;
private IUserDao userDao;
/**
* 初始化操作
*/
@Before //每一個測試之前都需要初始化
public void init() throws IOException {
//1.讀取映射配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.構建SqlSessionFactory工廠對象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.構建SqlSession會話對象
session = factory.openSession();
//4.構建代理IUserDao對象
userDao = session.getMapper(IUserDao.class);
}
/**
* 測試查詢所有用戶
*/
@Test
public void testFindAllUsers(){
List<User> users = userDao.findAllUsers();
for(User user : users){
System.out.println(user);
}
}
@After //在每一個測試之後都需要釋放資源
public void destroy() throws IOException {
//釋放資源
session.close();
in.close();
}
}
測試結果即查詢到所有的用戶信息。
2.6 基於註解的接口及查詢所有用戶功能測試
基於註解主要是不使用映射文件,映射配置文件中的mapper使用class屬性,值爲接口的全限定路徑。
public interface IUserDao {
/**
* 查詢所有用戶
* @return
*/
@Select("select * from user") //通過此註解完成對數據庫的操作。package+方法名對比xml中的namespace,接口方法對比xml中的id,註解中的sql對比xml中的sql,方法的參數和返回值對比xml中的parameterType和resultType
List<User> findAllUsers();
}
基於註解的方法測試同基於xml的方法測試,測試結果相同。後續其他CRUD功能都會分別以基於XML和基於註解進行說明。
2.7 一般開發中使用上述中的代理模式進行開發,但是也可以通過daoImpl實現,此處以基於xml方式+daoimpl進行簡單說明一下
創建實現IUserDao的實現類IUserDaoImpl,並在方法中創建SqlSession。調用SqlSession的selectList方法,參數爲namespace+id。SqlSession還支持insert/selectOne/delete/update方法, 參數也可以加Object對象(例如insert的參數爲路徑和user對象)
public class IUserDaoImpl implements IUserDao {
private SqlSessionFactory sessionFactory;
/**
* 通過構造函數保證SqlSessionFactory不爲空
* @param sqlSessionFactory
*/
public IUserDaoImpl(SqlSessionFactory sqlSessionFactory){
this.sessionFactory = sqlSessionFactory;
}
public List<User> findAllUsers() {
//1.使用工廠創建SqlSession對象
SqlSession session = sessionFactory.openSession();
//2.使用session執行查詢所有方法
List<User> users = session.selectList("com.practice.dao.IUserDao.findAllUsers");
//釋放資源
session.close();
//3.返回查詢結果
return users;
}
}
由於這種開發比較繁瑣,此處只是簡單說明。目前用到的基本都是基於xml的開發模式。
2.8 typeAliases與package的使用
在使用中可能會有多個實體類,爲了xml中參數方便不再重複寫全限定路徑,可以中typeAiases標籤在映射配置文件中進行設置。
如果實體類比較多的話,可以使用package標籤。
另外,映射配置文件中mapper標籤也支持package
3. MyBatis的基本操作
主要是記錄xml和註解的表達,測試和上述測試類似,差別的地方在於調用接口的不同方法,不再贅述。xml和註解兩種開發不可並存,否則運行會報錯(即不能有註解之後還有xml映射文件)
3.1 根據ID查詢用戶
3.1.1 基於xml
<!-- 根據用戶ID查詢用戶 -->
<select id="findById" parameterType="LONG" resultType="com.practice.domain.User">
select * from user
where userId=#{userId}
</select>
3.1.2 基於註解
@Select("select * from user where userId=#{userId}")
@ResultMap(value = {"userMap"})
User findByUserId(Long userId);
3.2 根據用戶名模糊查詢用戶
3.2.1 基於XML
<!-- 根據用戶名模糊查詢用戶 -->
<!-- preparedStatement對象 -->
<select id="findByName" parameterType="java.lang.String" resultType="com.practice.domain.User">
select * from user
where username like #{username}
</select>
<!-- statement對象 -->
<!-- <select id="findByName" parameterType="java.lang.String" resultType="com.practice.domain.User">-->
<!-- select * from user-->
<!-- where username like '%${value}%'-->
<!-- </select>-->
3.2.2 基於註解
@Select("select * from user where username like #{username}") //preparedStatement,較爲常用
// @Select("select * from user where username like '%${value}%' ") //statement
@ResultMap("userMap")
List<User> findByUsername(String username);
3.3 增加用戶
3.3.1 基於XML
<!-- 添加一個用戶 -->
<insert id="saveUser" parameterType="USER">
<!-- 配置插入操作後,返回插入數據的ID -->
<selectKey keyProperty="userId" keyColumn="userId" resultType="LONG" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,password,birthday,sex,address)
values(#{username},#{password},#{birthday},#{sex},#{address})
</insert>
3.3.2 基於註解
@Insert("insert into user(username,password,birthday,sex,address) " +
"values(#{username},#{password},#{birthday},#{sex},#{address})")
void saveUser(User user);
3.4 更新用戶
3.4.1 基於XML
<!-- 更新一個用戶 -->
<update id="updateUser" parameterType="uSer">
update user set
username=#{username},password=#{password},birthday=#{birthday},sex=#{sex},address=#{address}
where userId=#{userId}
</update>
3.4.2 基於註解
@Update("update user set username=#{username},password=#{password}," +
"birthday=#{birthday},sex=#{sex},address=#{address} where userId=#{userId}")
void updateUser(User user);
3.5 刪除用戶
3.5.1 基於XML
<!-- 刪除一個用戶 #{佔位符是可以隨意定義的}-->
<delete id="deleteUser" parameterType="java.lang.Long">
delete from user
where userId=#{userId}
</delete>
3.5.2 基於註解
@Delete("delete from user where userId=#{userId}")
void deleteUser(Long userId);
3.6 resultMap的使用
一般開發中會遇到實體類的屬性名和數據庫字段名不一致的情況,可以通過ResultMap進行映射匹配,例如數據庫中user_id對應實體類中的userId,分別基於xml和基於註解進行屬性映射
3.6.1 基於XML
<!-- 配置 查詢結果的列名和實體類屬性名的對應關係 -->
<resultMap id="userMap" type="com.practice.domain.User">
<!-- 主鍵字段的對應 -->
<id property="user_id" column="user_id"></id>
<!-- 非主鍵字段的對應 -->
<result property="userName" column="username"></result>
<result property="password" column="password"></result>
<result property="birthday" column="birthday"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
</resultMap>
<!-- 使用提前配置數據庫字段與實體類屬性名的對應關係後,此處resultType需要改成userMap(即導入對應關係配置)
parameterMap同理,如果參數爲實體類的話需要使用parameterMap="userMap"-->
<select id="findAllUsers" resultMap="userMap">
select * from user
</select>
3.6.2 基於註解
@Select("select * from user")
@Results(id="userMap", //聲明id之後其他使用該映射規則的直接使用resultMap,然後寫入這個id即可
value = {@Result(id = true,column = "user_id",property = "userId"),
@Result(column = "username",property = "username"), //非主鍵屬性,id默認爲false
@Result(column = "password",property = "password"),
@Result(column = "birthday",property = "birthday"),
@Result(column = "sex",property = "sex"),
@Result(column = "address",property = "address")}
)
List<User> findAllUsers();
4. MyBatis的多表查詢操作
4.1 多對一:例如多個賬戶可以隸屬於同一個用戶
在MyBatis中多對一可以看作一對一,對於一個賬戶而言,只有一個用戶。實現功能:查詢所有的賬戶並返回賬戶對應的用戶信息,通過關聯屬性
4.1.1 在賬戶Account實體類中建立關聯屬性User
4.1.2 基於xml實現關聯
<resultMap id="accountUserMap" type="com.practice.domain.Account">
<id property="accountId" column="accountId"></id>
<result property="userId" column="userId"></result>
<result property="money" column="money"/>
<!-- 一對一的關係映射,配置封裝user的內容 -->
<association property="user" column="userId" javaType="com.practice.domain.User">
<id property="userId" column="userId"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</association>
</resultMap>
4.1 基於註解實現關聯
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id=true,column = "accountId",property = "accountId"),
@Result(column = "userId",property = "userId"),
@Result(column = "money",property = "money"),
@Result(property = "user",column = "userId",
one = @One(select="com.practice.dao.IUserDao.findByUserId"))
})
List<Account> findAllAccounts();
4.2 一對多:例如一個用戶可以擁有多個賬戶
實現功能:查詢所有用戶並返回用戶對應的所有賬戶信息,通過集合屬性
4.2.1 User實體類中創建集合屬性Account
4.2.2 基於xml實現集合
<!-- 定義User的resultMap -->
<resultMap id="userMap" type="com.practice.domain.User">
<id property="userId" column="userId"></id>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="roles" ofType="role">
<id property="roleId" column="roleId"></id>
<result property="rolename" column="rolename"></result>
</collection>
</resultMap>
4.2.3 基於註解實現集合
@Select("select * from user")
@Results(id="userMap",
value = {@Result(id = true,column = "userId",property = "userId"),
@Result(column = "username",property = "username"),
@Result(column = "password",property = "password"),
@Result(column = "birthday",property = "birthday"),
@Result(column = "sex",property = "sex"),
@Result(column = "address",property = "address"),
@Result(property = "accounts",column = "userId",
many = @Many(select="com.practice.dao.IAccountDao.findByUserId"))}
)
List<User> findAllUsers();
查詢的時候直接調用User對象的getAccounts方法即可。
4.3 多對多:可以劃分爲兩個一對多關係,分別使用集合屬性完成,此處不再贅述
5. MyBatis中的連接池
MyBatis支持三種數據庫連接方式,POOLED/UNPOOLED/JNDI
5.1 POOLED
採用傳統的java.sql.DataSource規範中的連接池,MyBatis中有針對規範的實現
5.2 UNPOOLED
採用傳統的獲取連接的方式,雖然也實現java.sql.DataSource接口,但是並沒有使用池的思想,只是單純建立數據庫連接
5.3 JNDI
採用服務器提供的JNDI技術來獲取DataSource對象,不同的服務器所能拿到的DataSource不同。如果不是web/maven的war工程是不能使用的,tomcat服務器中採用連接池爲dbcp連接池
6. MyBatis中的加載
6.1 立即加載
不管用不用,只要一調用方法,馬上發起查詢。一般一對一使用立即加載。
6.1.1 基於XML的立即加載
<resultMap id="accountUserMap" type="com.practice.domain.Account">
<id property="accountId" column="accountId"></id>
<result property="userId" column="userId"></result>
<result property="money" column="money"/>
<!-- select屬性指定的內容:查詢用戶的唯一標識
column屬性指定的內容:用戶根據id查詢時所需要的參數值-->
<association property="user" column="userId" javaType="user"
select="com.practice.dao.IUserDao.findById"></association>
</resultMap>
<select id="findAllAccounts" resultMap="accountUserMap">
select * from account
</select>
6.1.2 基於註解的立即加載,FetchType爲EAGER
@Select("select * from account")
@Results(id = "accountMap", value = {
@Result(id=true,column = "accountId",property = "accountId"),
@Result(column = "userId",property = "userId"),
@Result(column = "money",property = "money"),
@Result(property = "user",column = "userId",
one = @One(select="com.practice.dao.IUserDao.findByUserId",fetchType = FetchType.EAGER))
})
List<Account> findAllAccounts();
6.2 延遲加載
在真正使用數據時才發起查詢,不用的時候不查詢,按需加載(懶加載)。一般一對多使用延遲加載
無論是xml還是註解,都需要在映射配置文件中設置開啓延遲加載。
6.2.1 基於XML的延遲加載
<!-- 定義User的resultMap -->
<resultMap id="userMap" type="com.practice.domain.User">
<id property="userId" column="userId"></id>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="account" ofType="account"
select="com.practice.dao.IAccountDao.findAccountByUserId" column="userId"></collection>
</resultMap>
<select id="findAllUsers" resultMap="userMap">
select * from user
</select>
<!-- 查詢用戶直接通過這個select查,如果需要查詢賬戶,則使用userId作爲參數去IAccountDao的映射文件中去找findAccountByUserId去查 -->
6.2.2 基於註解的延遲加載,FetchType爲LAZY
@Select("select * from user")
@Results(id="userMap",
value = {@Result(id = true,column = "userId",property = "userId"),
@Result(column = "username",property = "username"),
@Result(column = "password",property = "password"),
@Result(column = "birthday",property = "birthday"),
@Result(column = "sex",property = "sex"),
@Result(column = "address",property = "address"),
@Result(property = "accounts",column = "userId",
many = @Many(select="com.practice.dao.IAccountDao.findByUserId",fetchType = FetchType.LAZY))}
)
List<User> findAllUsers();
7. MyBatis中的緩存
7.1 一級緩存
MyBatis中SqlSession對象的緩存。當執行查詢之後,查詢的結果會同時存入到SqlSession提供的一塊區域中,區域的結構是一個Map。當再次查詢相同數據時MyBatis會先去SqlSession中查詢是否有,有的話會直接拿來用。當SqlSession消失後這個緩存Map也會隨之消失。
7.2 二級緩存
MyBatis中SqlSessionFactory對象的緩存,由同一個SqlSessionFactory對象創建的SqlSession共享其緩存。
7.2.1 基於XML開發中二級緩存的開啓方式
1. 在映射配置文件配置讓MyBatis支持二級緩存
2. 在映射文件中配置
3. 在具體的方法中配置
7.2.2 基於註解開發中二級緩存的開啓方式
1. 在映射配置文件中配置
2. 在接口類中使用註解進行配置
總結
千里之行始於足下,積少成多,認真踏實,好好學習,努力進步。