Mybatis筆記
一、概述
框架:封裝通用功能,軟體開發中的半成品,簡化開發過程。
輕量級的,持久層框架,負責完成java和資料庫的通訊。
程式碼分布:DAO+Service
MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,並且改名為MyBatis 。2013年11月遷移到Github。
iBATIS一詞來源於「internet」和「abatis」的組合,是一個基於Java的持久層框架。iBATIS提供的持久層框架包括SQL Maps和Data Access Objects(DAO)
1. 誕生背景
Java的原生資料庫通訊API( jdbc ),使用過於繁瑣,而且隨著數據變得複雜越發變得繁冗。
如下一個極其簡單的查詢動作,足以說明問題:
// 1> 自己管理 所有底層對象:驅動類,Connection,Statement,ResultSet
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("mysql:jdbc://localhost:3306/db9?\
useUnicode=true&characterEncoding=utf8");
PreparedStatement preparedStatement = connection.prepareStatement("select id,name,age from t_user");
ResultSet resultSet = preparedStatement.executeQuery();
// 2> 自己處理數據轉換過程:【 數據表格 ==> java對象 】
ArrayList<User> users = new ArrayList<User>();
while(resultSet.next()){
Integer id = resultSet.getInt("id");
String name = resultSet.getString("name");
Integer age = resultSet.getInt("age");
User user = new User(id,name,age);
users.add(user);
}
2. ORM
概念:Object Relational Mapping,對象關係映射。
目標:在【java對象】 和 【關係表】 建立映射
:簡化兩者之間的通訊過程。可以直接通訊。( ops: 如上的過程不再用自己處理 )
細節:ORM是持久層框架(MyBatis,Hibernate),的重要底層設計思路。為簡化持久層開發提供驅動
: java持久層框架將jdbc納入底層,然後上層架設orm,使開發者脫離jdbc的繁瑣
MyBatis對ORM的踐行方式: |
---|
![]() |
二、編碼過程
- Jdk環境:jdk1.8
- 資料庫環境:MySQL 5.1
- Mybatis:3.4.5
1. 搭建流程
1.1 導入依賴
// pom.xml
<!-- 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.25</version>
</dependency>
//pom.xml,使得src/main/java下的xml文件可以進入編譯範圍
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
</project>
1.2 定義O和R
create table t_user(
id int primary key auto_increment,
name varchar(20),
gender char(1),-- tinyint(1)
create_time datetime
)default charset=utf8 engine=innodb;
class User{
private Integer id;
private String name;
private Boolean gender;
private Date createTime;
//get/set....
}
1.3 M-定義映射文件
DAO介面定義不變,映射文件即Mapper文件替換之前的DAO實現
public interface UserDAO {
public List<User> queryAll();
public User queryOne(@Param("id") Integer id);
public List<User> queryManyByName(@Param("name") String name);
public List<User> queryManyByDate(@Param("createTime") Date craeteTime);
// 明確為參數定義別名,用於mapper文件中獲取
public List<User> queryUserByIdAndName(@Param("id") Integer id, @Param("name") String name);
}
// UserDAO.xml 重點搞清楚 何為映射
<!-- 不用定義dao的實現類,此映射文件等價於dao的實現 -->
<!-- namespace="dao介面路徑"-->
<?xml version="1.0" encoding="UTF-8"?>
<!-- dtd:document type definition 配置文件規範 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhj.dao.UserDAO">
<!--
根據id查詢用戶,User queryOne(Integer id)
select:配置查詢語句
id:可以通過id找到執行的statement,對應dao中的一個方法名
parameterType: 參數類型 【int long float double string boolean date 自建類型(com.zhj.domain.User)】
resultType:結果類型,查詢的結果會被封裝到對應類型的對象中
#{}:相當於佔位符
#{id}:就是把 「queryOne」 方法的名為id的參數填充到佔位上
-->
<select id="queryOne" parameterType="int" resultType="com.zhj.domain.User">
select id as id,name as name,gender as gender,create_time as createTime
from t_user
where id=#{id}
</select>
<!-- 注意:返回類型實際是List<User> , 但此處只需定義User即可 -->
<select id="queryAll" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from t_user
</select>
<select id="queryManyByName" parameterType="string" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from t_user
where name like #{name}
</select>
<select id="queryManyByDate" parameterType="date" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from t_user
where create_time=#{createTime}
</select>
<!-- 注意:此時方法有多個參數,【#{xx}】取值時,需要@param支援 -->
<select id="queryUsers" resultType="com.qianfeng.pojo.User"
parameterType="com.qianfeng.pojo.User">
select id,name,gender,birth from t_user where id=#{id} or name like #{name}
</select>
</mapper>
1.4 搭建配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"//mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 使用 id為「development」的環境資訊 -->
<environments default="development">
<environment id="development">
<!-- 配置JDBC事務控制,由mybatis進行管理 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置數據源,採用mybatis連接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<!-- 【&】是特殊字元,【&】是【&】的轉義 -->
<property name="url" value="jdbc:mysql://localhost:3306/db9?useUnicode=true&characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="111111" />
</dataSource>
</environment>
</environments>
<!-- 載入映射文件 -->
<mappers>
<!-- 使用資源的路徑
<mapper resource="com/zhj/dao/UserDAO.xml"/> -->
<!-- 載入某個包下的映射文件 (推薦)
要求:Mapper介面的名稱與映射文件名稱一致 且 必須在同一個目錄下 -->
<package name="com.zhj.dao"/>
</mappers>
</configuration>
2. 測試使用
2.1 核心API
SqlSessionFactoryBuilder
:該對象負責載入MyBatis配置文件 並 構建SqlSessionFactory實例SqlSessionFactory
:每一個MyBatis的應用程式都以一個SqlSessionFactory對象為核心。負責創建SqlSession對象實例。SqlSession
:等價於jdbc中的Connection,用於完成數據操作。
//1、讀取配置文件
String resource = "configuration.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//2、根據配置文件創建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3、SqlSessionFactory創建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
2.2 數據操作
- 通過SqlSession獲得DAO實現(Mapper)
- 通過DAO實現完成具體的數據操作
// 獲得UserDAO的實現
// UserDAO userMapper = sqlSession.getMapper(UserDAO.class);
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
//根據id查詢
userDAO.queryOne(1);
//查詢所有
userDAO.queryAll();
//根據姓名模糊查詢
userDAO.queryManyByName("%zhj%");
//根據日期查詢
Date date = new GregorianCalendar(2019, 11, 12).getTime();
userDAO.queryManyByDate(date);
回收資源:操作最後,關閉sqlSession
sqlSession.close();//關閉sqlSession
2.3 分頁操作
定義分頁資訊類:
class Page{
private Integer pageNum; //頁號
private Integer pageSize; //每頁幾條數據
private Integer offset; //偏移量,即從哪條查起
//offset的get方法中動態計算偏移量
public Integer getOffset() {
return (pageNum-1)*pageSize;
}
//其他set/get
}
定義DAO介面:
interface User{
...
public List<User> queryUserByPage(Page page);
}
定義Mapper:
<!-- #{offset} 取值時實際是會調用Page類中的getOffset()方法。 -->
<select id="queryUserByPage" parameterType="com.zhj.domain.Page" resultType="User">
select id,name,gender,create_time from t_user
limit #{offset},#{pageSize}
</select>
測試:
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
Page page = new Page(3,3);//第3頁,每頁3條
List<User> users = userDAO.queryUserByPage(page);
3. 增刪改操作
3.1 DAO介面
public interface UserDAO {
...
public Integer insertUser(User user);
public Integer deleteUser(Integer id);
public Integer updateUser(User user);
}
3.2 映射文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"//mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace="dao介面路徑"-->
<mapper namespace="com.zhj.dao.UserDAO">
....
....
<!-- 添加用戶
1>注意:#{方法參數對象的屬性名}
2>parameterType 和 resultType中都可以使用 預設包
3>增加時id補全兩種方式
-->
<insert id="insertUser" parameterType="com.zhj.domain.User">
<selectKey resultType="int" keyProperty="id" order="AFTER">
SELECT last_insert_id()
</selectKey>
insert into tt2 (name,gender,create_time)
values(#{name},#{gender},#{createTime})
</insert>
<insert id="insertUser2" parameterType="com.zhj.domain.User">
insert into tt2 (name,gender,create_time)
values(#{name},#{gender},#{createTime})
</insert>
<!--
刪除用戶
-->
<delete id="deleteUser" parameterType="int">
delete from tt2
where id=#{id}
</delete>
<!--
修改用戶
-->
<update id="updateUser" parameterType="com.zhj.domain.User">
update tt2 set name=#{name},gender=#{gender},create_time=#{createTime}
where id= #{id}
</update>
</mapper>
3.3 測試使用
注意:增刪改操作需要控制事務,否則操作不會同步到資料庫
//事務控制
SqlSession sqlSession = sqlSessionFactory.openSession();// 隨著session獲得,事務會自動開啟
.... //數據操作
.... //數據操作
sqlSession.commit();//提交事務
sqlSession.rollback();//回滾事務
增刪改操作
try{
....
userDAO.updateUser(user);//除了需要控制事務之外,和查詢操作使用無異
/**User u = new User(...);
userDAO.insertUser(u);**/
/**userDAO.deleteUser(1)**/
sqlSession.commit();//提交事務
}catch(Exception e){
e.printStackTrace();
sqlSession.rollback();//回滾事務
}finally{
if(sqlSession!=null){
sqlSession.close();//回收資源
}
}
3.4 增加細節
ID缺失!
User user = new User("zhj",true,new Date());//此時user沒有id
userDAO.insertUser(user);
sqlSession.commit();//此時數據已經插入到資料庫中,資料庫中有id,但user依然沒有id
System.out.println(user.getId());//沒有id
//則無法得知插入的數據是哪一條,如果後續程式需要此id,則出現bug!
兩種解決方案:
1> selectKey標籤
2> useGenerateKeys 和 keyProperty屬性
<insert id="insertUser" parameterType="com.zhj.domain.User">
<!-- AFTER:此中語句在插入語句之後執行
resultType=「int」: 此中語句執行後的返回類型是 int
keyProperty="id": 此中語句返回值要 傳遞給當前方法參數中的id屬性 (com.zhj.domain.User的id屬性)
select last_insert_id():mysql特性語句,返回當前事務中,最近一次插入的數據的id-->
<selectKey resultType="int" keyProperty="id" order="AFTER">
select last_insert_id()
</selectKey>
insert into t_user (name,gender,create_time)
values(#{name},#{gender},#{createTime})
</insert>
<!-- useGeneratedKeys="true" 聲明此處添加中id用的是自動增長
keyProperty="id" 將id值 傳遞給當前方法的參數中的id屬性 (com.zhj.domain.User的id屬性)-->
<insert id="insertUser" parameterType="com.zhj.domain.User" useGeneratedKeys="true"
keyProperty="id">
insert into t_user (name,gender,create_time)
values(#{name},#{gender},#{createTime})
</insert>
三、別名
在mapper文件中,
parameterType
和resultType
中使用類型時:
<select id="xxx" parameterType="com.zhj.domain.Page" resultType="com.zhj.domain.User">
除mybatis自動映射的類型外,其他類型都要定義完整路徑,相對繁瑣。可用如下兩種方式簡化:
// configuration.xml
<configuration>
...
<typeAliases>
<!-- 1. 單獨為每個類定義別名,則 "Page"等價於"com.zhj.domain.Page"
<typeAlias type="com.zhj.domain.Page" alias="Page"/>
<typeAlias type="com.zhj.domain.User" alias="User"/>-->
<!-- 2. 定義預設包,當mapper中的類型沒有定義包時,使用此配置作為默認包;
則 「Page」 會自動認為是 「com.zhj.domain」中的「Page」,即「com.zhj.domain.Page」
則 「User」 會自動認為是 「com.zhj.domain」中的「User」,即「com.zhj.domain.User」-->
<package name="com.zhj.domain"/>
</typeAliases>
...
</configuration>
有如上別名配置後,mapper中:
<select id="xxx" parameterType="Page" resultType="User">
四、參數分離
在mybatis的配置文件中有一項重要且可能需要經常改動的配置,即,資料庫連接參數。
可以單獨進行管理,方便後續維護。
<!-- 如下四項參數 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/db9?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root" />
<property name="password" value="111111" />
</dataSource>
1. 單獨定義參數文件
# 在resources目錄下,創建 jdbc.properties文件
# 參數名=參數值
jdbc.user=root
jdbc.password=111111
jdbc.url=jdbc:mysql://localhost:3306/db9?useUnicode=true&characterEncoding=utf8
jdbc.driver=com.mysql.jdbc.Driver
2. 載入參數文件
// 在 configuration.xml中
<configuration>
<!-- 載入參數文件 -->
<properties resource="jdbc.properties"></properties>
....
....
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!-- 使用 ${參數名} 獲取參數文件中的值。如此這四項參數需要修改時,需要改動參數文件即可 -->
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
五、關聯關係
項目中的表都不是孤立的,會彼此關聯。在操作數據時,需要聯合多張表。
我們需要將表間的關係映射清楚,mybatis可以提供很好的
多表聯合操作支援,
比如:
查詢所有用戶及其所有訂單
查詢所有學生及其所學課程
等操作,可以省去很多冗繁程式碼,直接拿到結果。
關聯關係種類:
1對1
1對多
多對多
Person(公民) Passport(護照)
1對1
User(用戶) Order(訂單)
1對多
Student(學生) Course(課程)
多對多
1. 一對一
1.1 建表
drop table IF EXISTS t_passport;
drop table IF EXISTS t_person;
create table t_person(
id int primary key AUTO_INCREMENT,
name VARCHAR(50),
age SMALLINT
)DEFAULT CHARSET = utf8 ENGINE =innodb;
create table t_passport(
id int primary key AUTO_INCREMENT,
note VARCHAR(50),
create_time DATE,
person_id int UNIQUE,
FOREIGN KEY(person_id) REFERENCES t_person(id)
)DEFAULT CHARSET = utf8 ENGINE =innodb;
insert into t_person(name,age) values("zhangsan",18);
insert into t_person(name,age) values("lisi",19);
insert into t_passport(note,create_time,person_id) values("pass1",'2019-05-07',1);
insert into t_passport(note,create_time,person_id) values("pass2",'2019-05-07',2);
1.2 建類
public class Person {
private Integer id;
private String name;
private Integer age;
//關係屬性
private Passport passport;
//set/get
}
public class Passport {
private Integer id;
private String note;
private Date createTime;
//關係屬性
private Person person;
//set/get
}
1.3 查詢
查詢某個Person,及其Passport
//PersonDAO :查詢某個Person的資訊及其Passport的資訊
public Person queryPersonAndPassport(Integer id);
//映射文件
<select id="queryPersonAndPassport" resultType="Person" parameterType="int">
select
t_person.id as pid,
name,
age,
t_passport.id as passid,
note,
create_time,
person_id
from t_person join t_passport
ON t_person.id = t_passport.person_id
where t_person.id=#{id};
</select>
如上映射文件,存在問題 ==> 關係屬性:passport,需要多個列映射到這一個屬性上,但並沒有映射清楚 |
---|
![]() |
若要完成上圖中的映射,不能再使用默認的同名映射規則,需要訂製映射規則。
resultMap
— 映射規則訂製利器
<!-- 定義查詢中的各個列 與 屬性的映射規則 -->
<!-- id=標識 type=返回類型(將列映射到User的屬性上) -->
<resultMap id="person_passport" type="Person">
<!-- 主鍵列pid,用 id標籤配置; property=屬性名 column=列名 -->
<id property="id" column="pid"/>
<!-- 常規列用 result標籤配置; property=屬性名 column=列名 -->
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 重點:關係屬性【Passport passport】是一個對象
用 association標籤配置,如下配置就是在完成上圖中 未完成之映射
property=屬性名 javaType=該屬性的類型 -->
<association property="passport" javaType="Passport">
<!-- 將passid列 映射到 passport的id屬性中 -->
<id property="id" column="passid"/>
<!-- 將note列 映射到 passport的note屬性中 -->
<result property="note" column="note"/>
<!-- 將create_time列 映射到 passport屬性的createTime屬性中 -->
<result property="createTime" column="create_time"/>
</association>
</resultMap>
<!-- 刪除之前的resultType, 改為使用resultMap="person_passport"
即,採用resultMap中定義的映射規則去封裝對象-->
<select id="queryPersonAndPassport" resultMap="person_passport" parameterType="int">
select
t_person.id as pid,
name,
age,
t_passport.id as passid,
note,
create_time,
person_id
from t_person join t_passport
ON t_person.id = t_passport.person_id
where t_person.id=#{id};
</select>
1.4 測試
// 查詢id=1的person及其passport
Person person = personDAO.queryPersonAndPassport(1);
// 從person中獲取passport
Passport passport = person.getPassport();
**思考題 ==> 請自主設計,完成以下: **
查詢所有Person及其Passport:public List<Person> queryAll();
查詢某個Passport及其Person數據:public Passport queryPassportAndPerson(Integer id)
細節:
增刪改,沒有任何特別需要改動的,正常定義<insert> <update> <delete> 即可
2. 一對多
2.1 建表
drop table IF EXISTS t_order;
drop table IF EXISTS t_user;
create table t_user( # 用戶表
id int primary key AUTO_INCREMENT,
name VARCHAR(50),
gender char(1),
regist_time DATE
)DEFAULT CHARSET = utf8 ENGINE =innodb;
create table t_order( # 訂單表
id int primary key AUTO_INCREMENT,
price VARCHAR(50),
note VARCHAR(50),
create_time DATE,
user_id int,
FOREIGN KEY(user_id) REFERENCES t_user(id)
)DEFAULT CHARSET = utf8 ENGINE =innodb;
insert into t_user(name,gender,regist_time) values('zhangsan','1','2019-12-12');
insert into t_user(name,gender,regist_time) values('lisi','0','2019-12-11');
insert into t_order(price,note,create_time,user_id) values(3000.55,'哈哈','2019-12-12',1);
insert into t_order(price,note,create_time,user_id) values(600.55,'哈哈2','2019-12-12',1);
insert into t_order(price,note,create_time,user_id) values(900.55,'呵呵2','2019-12-12',2);
insert into t_order(price,note,create_time,user_id) values(4000.55,'呵呵2','2019-12-12',2);
2.2 建類
public class User {
private Integer id;
private String name;
private Boolean gender;
private Date registTime;
//關係屬性,存儲用戶的多個訂單
private List<Order> orders;
//set/get
}
public class Order {
private Integer id;
private BigDecimal price;
private String note;
private Date createTime;
//關係屬性
private User user;
//set/get
}
2.3 查詢
//DAO
public queryOneUserAndOrders(Integer id);//根據用戶id查詢用戶及其所有訂單
<!-- 按照之前的思路,映射如此定義,但此時,resultType為User,所以只能接收User本身的資訊!
那如何將查詢中得到的Order資訊也保存到User中呢?
User中保存Order資訊的屬性名為「orders」,則此屬性需要有一個和它同名的列才可以接收值!
但是該屬性的值又不可能來自某一個列,而是多個列的值!
我們該如何配置,才可以將多列的值對應到一個屬性上呢?
-->
<select id="queryOneUserAndOrders" parameterType="int" resultType="User">
select t_user.id as uid,
name,
gender,
regist_time as registTime,
t_order.id as oid,
note,price,
create_time as createTime
from t_user join t_order
ON t_user.id = t_order.user_id
where t_user.id=#{id}
</select>
如上映射文件,存在問題 ==> 關係屬性:orders,需要多個列映射到這一個屬性上,但並沒有映射清楚 |
---|
![]() |
若要完成上圖中的映射,不能再使用默認的同名映射規則,需要訂製映射規則。
resultMap
— 映射規則訂製利器
<!-- 定義查詢中的各個列 與 屬性的映射規則 -->
<!-- id=標識 type=最終返回類型(將列映射到User的屬性上) -->
<resultMap id="user_orders" type="User">
<!-- 主鍵列單獨用id標籤標識
property=類中屬性名 column=查詢中的列名-->
<id property="id" column="uid"/>
<!-- 常規屬性都用result標籤 -->
<result property="name" column="name"/>
<result property="gender" column="gender"/>
<result property="registTime" column="registTime"/>
<!-- 重點:關係屬性【List<Order> orders】,非主鍵也非常規屬性,而是一個List
用collection標籤配置,用於完成上圖中未完成之映射。
property=屬性名 ofType=集合中的泛型類型-->
<collection property="orders" ofType="Order">
<!-- Order的主鍵列 -->
<id property="id" column="oid"/>
<!-- 常規屬性用result標籤 -->
<result property="note" column="note"/>
<result property="price" column="price"/>
<result property="createTime" column="createTime"/>
</collection>
</resultMap>
<!-- 此處是resultType換為resultMap,進而引用了如上定義的「user_orders」 -->
<select id="queryOneUserAndOrders" parameterType="int" resultMap="user_orders">
select t_user.id as uid,
name,
gender,
regist_time as registTime,
t_order.id as oid,
note,price,
create_time as createTime
from t_user join t_order
ON t_user.id = t_order.user_id
where t_user.id=#{id}
</select>
2.4 測試
//查詢用戶1,及其訂單資訊
User user = userDAO.queryOneUserAndOrders(1);
//獲取用戶1的訂單資訊
List<Order> orders = user.getOrders();
**思考題 ==> 請自主設計,完成以下: **
查詢所有User及其Order:public List<User> queryAll();
查詢某個Order及其User:public Order queryOrderAndUser(Integer orderid)
細節:
增刪改,沒有任何特別需要改動的,正常定義<insert> <update> <delete> 即可
3. 多對多
3.1 建表
drop table IF EXISTS t_student;
drop table IF EXISTS t_course;
drop table IF EXISTS t_student_course;
create table t_student(
id int primary key AUTO_INCREMENT,
name VARCHAR(50),
age SMALLINT
)DEFAULT CHARSET = utf8 ENGINE =innodb;
create table t_course(
id int primary key AUTO_INCREMENT,
title VARCHAR(50)
)DEFAULT CHARSET = utf8 ENGINE =innodb;
create table t_student_course(
student_id int,
course_id int,
FOREIGN KEY (student_id) REFERENCES t_student(id),
FOREIGN KEY (course_id) REFERENCES t_course(id),
PRIMARY KEY (student_id,course_id)
)DEFAULT CHARSET = utf8 ENGINE =innodb;
insert into t_student (name,age) values("zhangsan",18);
insert into t_student (name,age) values("lisi",19);
insert into t_course (title) values("java基礎");
insert into t_course (title) values("mybatis");
insert into t_student_course values(1,1);
insert into t_student_course values(1,2);
insert into t_student_course values(2,1);
insert into t_student_course values(2,2);
3.2 建類
public class Student {
private Integer id;
private String name;
private Integer age;
//關係屬性
private List<Course> courses;
//set/get
}
public class Course {
private Integer id;
private String title;
//關係屬性
private List<Student> students;
//set/get
}
public class StudentCourse {
private Integer studentId;
private Integer courseId;
//set/get
}
3.3 查詢
//StudentDAO
public List<Student> queryStudents();
//StudentDAO.xml 映射文件
<!-- 定義映射規則 和一對多中使用一樣-->
<resultMap id="student_course" type="Student">
<id column="sid" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<!-- 此處使用collection和一對多中一樣 -->
<collection property="courses" ofType="Course">
<id column="cid" property="id"/>
<result column="title" property="title"/>
</collection>
</resultMap>
<!-- 查詢,並根據映射規則完成數據封裝 -->
<select id="queryStudents" resultMap="student_course">
select
t_student.id as sid,
name,
age,
t_course.id as cid,
title
from t_student join t_student_course
ON t_student.id = t_student_course.student_id
JOIN t_course
ON t_student_course.course_id = t_course.id;
</select>
3.4 測試
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
//查詢所有學生及其所有課程
List<Student> students = studentDAO.queryStudents();
//獲取每個學生的所有課程
for(Student stu:students){
System.out.println(stu);
for(Course cour:stu.getCourses()){
System.out.println(cour);
}
}
3.5 增加測試
//StudentDAO.xml
<mapper namespace="com.zhj.dao.StudentDAO">
<!-- 增加Student到 t_student表 ( ops:後續操作需要新增學生的id )-->
<insert id="insertStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="id">
insert into t_student(name,age) values(#{name},#{age})
</insert>
</mapper>
//StudentCourseDAO.xml
<mapper namespace="com.zhj.dao.StudentCourseDAO">
<!-- 為學生添加課程 或 為課程添加學生 -->
<insert id="insertStudentCourse" parameterType="StudentCourse">
insert into t_student_course(student_id,course_id) values(#{studentId},#{courseId})
</insert>
</mapper>
//添加學生 趙六,並為其關聯課程 1
// 獲得 學生DAO 和 學生課程DAO
StudentDAO studentDAO = sqlSession.getMapper(StudentDAO.class);
StudentCourseDAO scDAO = sqlSession.getMapper(StudentCourseDAO.class);
// 新建學生,並添加
Student student = new Student(null, "趙六", 22);
studentDAO.insertStudent(student);
// 新建學生趙六和課程1的關係,並添加 (為趙六添加課程 1)
StudentCourse sc = new StudentCourse(student.getId(),1);
scDAO.insertStudentCourse(sc);
// 提交
sqlSession.commit();
4. 關係的方向(了解)
單向關係:A B 雙方,A中有B,或B中有A
雙向關係:A B雙方,A中有B,且B中有A
//互相持有彼此,即為雙向關係
class User{
...
private List<Order> orders;
}
class Order{
...
private User user;
}
//Order不持有User,或User不持有Order,即為單向關係
class User{
...
private List<Order> orders;
}
class Order{
...
}
六、# 和 $
1. 各自特點
//如果用$,則必須用 @Param註解,否則${name}會認為是要從參數中取名為name的屬性
public List<User> test3(@Param("name") String a);
<!-- 注意${} 就是在做字元拼接,所以此處用了【『${name}』】而不是【${name}】
類比jdbc的sql語句的拼接:
String name="zhj";
String sql = "select ... from tt2 where name='"+name+"'";//此處是要加單引號的
注意:sql拼接時,有sql注入的風險
-->
<select id="test3" parameterType="string" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from tt2
where name = '${name}'
</select>
<!-- 注意#{} 就是在做佔位符,所以此處用了【#{name}】而不是【』#{name}『】
類比jdbc的sql語句的拼接:
String name="zhj";
String sql = "select ... from tt2 where name=?「;//此處是不加單引號的
...
prepareStatement.executeQuery(sql,name);//佔位符賦值
-->
<select id="test3" parameterType="string" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from tt2
where name = #{name}
</select>
2. 使用$場景
<select id="test3" parameterType="string" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from tt2
order by id ${name}
</select>
<select id="test4" parameterType="string" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from ${tn}
where name = #{name}
</select>
<!-- 用在列名上亦可 -->
userDAO.test3("desc");
userDAO.test3("asc");
userDAO.test4("t_user");
userDAO.test4("t_admin");
八、動態sql
在映射文件中,定義了要執行的sql語句,mybatis支援在sql語句中填充一些邏輯,是的sql語句可以呈現不同的語義,
即動態sql
1. IF
在sql中 注入
if
,可以讓sql更加靈活,讓一個查詢語句,可以應對更多查詢情景。重點:== != > < >= <= and or
: 比較字元串需要加引號,比較數字、布爾、null不用加引號
常用:對參數是否為空的判斷,動態決定sql語句的組成
情景:對用戶可以有通過name 或 gender的搜索,如果沒有
if
動態邏輯,則是要定義多個<select>
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user
WHERE
<if test="name != null and name!=''">
name=#{name}
</if>
<if test="gender != null">
AND gender=#{gender}
</if>
</select>
<!-- 如上如果gender為null,name不為null,則sql為:
SELECT id,name,gender,regist_time
FROM t_user
WHERE name=#{name}
-->
<!-- 如上如果gender不為null,name不為null,則sql為:
SELECT id,name,gender,regist_time
FROM t_user
WHERE name=#{name} AND gender=#{gender}
-->
<!-- 補充:比較的其他用法 -->
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user
WHERE
<if test="name == null or name=='zhj'">
name=#{name}
</if>
<if test="id>=1">
AND id>#{id}
</if>
<if test="gender == false">
AND gender=#{gender}
</if>
</select>
2. Choose
如果在多個判斷中,只會有一個是可以成立的,可以使用Choose標籤
<!-- 如果id不為空就按id查詢。如果id為空但name不為空就按name查詢。如果都為空,就查詢所有男性用戶。 -->
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user
WHERE
<choose> <!-- 從上到下,有任何一個when標籤,判斷成功則停止判斷 -->
<when test="id != null"> <!-- 判斷 -->
id > #{id}
</when>
<when test="name !=null"> <!-- 判斷 -->
name = #{name}
</when>
<otherwise> <!-- 以上判斷都不成立時,執行 -->
gender = '1'
</otherwise>
</choose>
</select>
注意,在sql中的判斷,本意不在於判斷,而在於將sql動態的適應不同場景,簡化開發。
3. Where
動態sql在使用中,存在一些問題:
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time FROM t_user
WHERE
<if test="name != null">
name=#{name}
</if>
<if test="id>=1">
AND id>#{id}
</if>
</select>
<!-- 如果 name=null,id=3,則sql變為:
SELECT id,name,gender,regist_time FROM t_user
WHERE AND id>#{id}
-->
<!-- 如果 name=null,id=0,則sql變為:
SELECT id,name,gender,regist_time FROM t_user
WHERE
-->
<where>
標籤中如果沒有成立的條件,則不會拼接where語句
;
<where>
標籤中如果以 and 或 or開頭,會去將其去除。
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user
<where>
<if test="name == null">
name=#{name}
</if>
<if test="id>=1">
AND id>#{id}
</if>
</where>
</select>
<!-- 如果 name=null,id=3,則sql變為:
SELECT id,name,gender,regist_time FROM t_user
WHERE id>#{id}
-->
<!-- 如果 name=null,id=0,則sql變為:
SELECT id,name,gender,regist_time FROM t_user
-->
4. Set
<set>
標籤主要用於update
中
<!-- 只更新非空的欄位 -->
<update id="updateUser" parameterType="User">
UPDATE t_user2
SET
<if test="name != null">
name = #{name},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="registTime != null">
regist_time = #{registTime}
</if>
WHERE id = #{id}
</update>
<!-- 如果registTime=null,name或gender!=null,則sql為:
UPDATE t_user2
SET name = #{name},gender = #{gender},
WHERE id = #{id}
注意:多了逗號,sql語法錯誤
-->
使用
<set>
:
<update id="updateUser" parameterType="User">
UPDATE t_user2
<set>
<if test="name != null">
name = #{name},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="registTime != null">
regist_time = #{registTime}
</if>
</set>
WHERE id = #{id}
</update>
<!-- 如果registTime=null,name或gender!=null,則sql為:
UPDATE t_user2
SET name = #{name},gender = #{gender}
WHERE id = #{id}
注意:<set>會自動補充一個 」set語句「,並將最後一個逗號去除
-->
5. Trim
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user2
<trim prefix="where" prefixOverrides="and|or">
<if test="name != null">
name=#{name}
</if>
<if test="id>=10">
OR id>#{id}
</if>
<if test="gender == false">
AND gender=#{gender}
</if>
</trim>
</select>
<!-- <trim prefix="where" prefixOverrides="and|or"> 等價於 <where>
prefix="where":會添加一個where關鍵字
prefixOverrides="and|or":會去除開頭的and 或 or
-->
<update id="updateUser" parameterType="User">
UPDATE t_user2
<trim prefix="set" suffixOverrides=",">
<if test="name != null">
name = #{name},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="registTime != null">
regist_time = #{registTime}
</if>
</trim>
WHERE id = #{id}
</update>
<!-- <trim prefix="set" suffixOverrides=","> 等價於 <set>
prefix="set":會添加一個set關鍵字
suffixOverrides=",":會去除最後的逗號
-->
6. Foreach
批量查詢:id為 1,3,5的數據
//DAO聲明為: public List<User> queryUsers2(List<Integer> ids);
<select id="queryUsers2" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user2
WHERE id IN
<foreach collection="list" open="(" separator="," close=")" item="id" index="ind">
#{id}
</foreach>
</select>
<!--
<foreach collection="list" open="(" separator="," close=")" item="id" index="ind">
collection="list" 代表參數是List,如果是數組,要用array
open="(" 以「(」 開頭
close=")" 以「)」 結尾
separator="," 值之間用「,」分隔
item="id" 每次遍歷的值的臨時變數
#{id} 獲取每次遍歷的值
如上:標籤的效果是 (值1,值2,值3)
示例:如果傳入 List{1 3 5},則最終的sql:
【SELECT id,name,gender,regist_time
FROM t_user2
WHERE id IN (1,3,5)】
-->
批量添加:
insert into t_user (name,gender) values(‘xx’,1),(‘xxx’,0),(‘xxxx’,1)
<!-- 批量添加 -->
<insert id="insertUsers" parameterType="java.util.List">
insert into t_user2 (name,gender,regist_time) values
<foreach collection="list" item="user" index="ind" close="" open="" separator=",">
(#{user.name},#{user.gender},#{user.registTime})
</foreach>
</insert>
List<User> users = ...;
System.out.println(userDAO.insertUsers(users));
7. Sql
復用sql語句
<!-- 定義一段sql -->
<sql id="order">
id,note,price,create_time as createTime
</sql>
<!-- 引用sql -->
<select id="queryOrder" parameterType="int" resultType="Order">
select
<include refid="order"/>
from t_order
where id = #{id}
</select>
九、快取
快取:將資料庫的數據臨時的存儲起來,以更好的支援查詢。
問題:如果有數據,查詢頻繁且更新極少,此種數據如果依然每次到資料庫查詢,效率偏低。
解決:將如上數據,臨時的存儲到記憶體中,提供對外界的查詢服務,進而減少和資料庫的通訊,提高查詢效率。
原理:當查詢數據時,查詢結果會被快取在某個記憶體區域中,核心存儲結構={sql:查詢結果};
每次發起查詢時,會先找到快取,從中嘗試獲取數據,如果沒有找到數據,再去查資料庫,並將在數**
庫中查到的結果存入快取,以供後續查詢使用。
MyBatis作為持久層框架,快取管理自然是他的本職工作。
支援了兩種快取:
一級快取,二級快取
1. 一級快取
存儲位置:SqlSession;即一個SqlSession對象發起查詢後,查詢結果會快取在自己內部
有效範圍:同一個SqlSession的多次查詢;即,同一個SqlSession的多次相同sql的查詢可以使用一級快取
開啟:不用任何配置,默認啟動。
清除:
sqlSession.clearCache();
2. 二級快取
2.1 概述
**存儲位置:SqlSessionFactory;同一個SqlSessionFactory創建的所有SqlSession發起的查詢,查詢結果都會快取在 **
SqlSessionFactory內部。
有效範圍:同一個SqlSessionFactory
開啟:默認開啟,但需要制定哪個DAO的Mapper需要使用二級快取,定義一個
<cache>
即可注意:二級快取必須在
sqlSession.commit()
或sqlSession.close()
之後才生效清除:
sqlSession.rollback();//則查詢的結果不會進入二級快取
2.2 應用
二級快取使用:
<mapper namespace="com.zhj.dao.UserDAO">
<!-- 當前mapper中的所有查詢,都進入二級快取
快取數據中涉及的pojo一定要實現 Serialiable。
-->
<cache></cache>
<select>...</select>
.....
</mapper>
UserDAO userDAO1 = sqlSession1.getMapper(UserDAO.class);
UserDAO userDAO2 = sqlSession2.getMapper(UserDAO.class);
userDAO1.queryOne(1);
userDAO2.queryOne(1);
// 在開啟了二級快取的情況下,如上程式碼依然會查詢兩次資料庫。
// userDAO1.queryOne(1);之後快取只在sqlSession1中,並未進入二級快取。userDAO2.queryOne(1);無法使用
UserDAO userDAO1 = sqlSession1.getMapper(UserDAO.class);
UserDAO userDAO2 = sqlSession2.getMapper(UserDAO.class);
userDAO1.queryOne(1);
sqlSession1.commit();//close()也可以,因為close內部流程和commit內部流程有對快取的相同處理
userDAO2.queryOne(1);
// 此時如上程式碼會只查詢一次資料庫。
// sqlSession1.commit();執行時,會將查到的數據序列化,存入二級快取中。userDAO2.queryOne(1)可以使用
思考題:如下程式碼會如何查詢資料庫 ?
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserDAO userDAO= sqlSession.getMapper(UserDAO.class);
UserDAO userDAO2= sqlSession2.getMapper(UserDAO.class);
UserDAO userDAO3= sqlSession3.getMapper(UserDAO.class);
userDAO.queryOne(1);
userDAO.queryOne(2);
sqlSession.close();
userDAO2.queryOne(3);
sqlSession2.close();
userDAO3.queryOne(1);
userDAO3.queryOne(2);
userDAO3.queryOne(3);
//請分析如上查詢,會觸發哪些資料庫查詢
2.3 結構
二級快取存儲結構 |
---|
![]() |
2.4 清除
二級快取是以
namespace
為單位組織的,當某個namespace
中發生數據改動,則namespace
中快取的所有數據會被mybatis清除。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis_config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserDAO userMapper = sqlSession.getMapper(UserDAO.class);
UserDAO userMapper2 = sqlSession2.getMapper(UserDAO.class);
UserDAO userMapper3 = sqlSession3.getMapper(UserDAO.class);
userMapper.queryUserById(1);
sqlSession.close();//二級快取生效
// userMapper中的所有二級快取被清除
userMapper2.updateUser(new User(1,"zs",true,new Date()));
sqlSession2.commit();
// 再次查詢,二級快取中已沒有數據,會查詢資料庫
userMapper3.queryUserById(1);
2.5 cache-ref
和關係屬性相關
注意如果
<collection>
中沒有使用select
關聯查詢,則不存在此問題。
<mapper namespace="com.zhj.dao.UserDAO">
<cache/>
<resultMap id="user_orders" type="User">
<id property="id" column="uid"/>
<result property="name" column="name"/>
<result property="gender" column="gender"/>
<result property="registTime" column="registTime"/>
<collection property="orders" select="com.zhj.dao.OrderDAO.queryOrderOfUser" column="id" fetchType="eager/lazy"/>
</resultMap>
<select id="queryOne" parameterType="int" resultMap="user_orders">
select id,name,gender,regist_time as registTime
from t_user
where id=#{id}
</select>
</mapper>
<mapper namespace="com.zhj.dao.OrderDAO">
<!-- 使用cache-ref 則OrderDAO的快取數據,會存放於com.zhj.dao.UserDAO分支下,
與UserDAO的快取數據存儲於同一個Perpetual對象中
-->
<cache-ref namespace="com.zhj.dao.UserDAO"/>
<select id="queryOrderOfUser" parameterType="int" resultType="Order">
select id as oid,note,price,create_time as createTime
from t_order
where user_id = #{userId}
</select>
</mapper>
UserDAO userDAO = sqlSession1.getMapper(UserDao.class);
//User 和 關係屬性Order 都被快取
User user = userDAO.queryOne(1);
user.getOrders().size();
sqlSession.commit();
OrderDAO orderDAO = sqlSession2.getMapper(OrderDAO.lass);
orderDAO.queryOrderOfUser(1); //有二級快取數據可用 (OrderDAO中必須有 <cache>或<cache-ref>)
UserDAO userDAO = sqlSession1.getMapper(UserDao.class);
userDAO.queryOne(1);
//數據改動,此時會清空User快取,並清空Order快取(因為order中是 <cache-ref>,如果是<cache>則此處只會清空User快取)
userDAO.insertUser(new User(null,"new_user",true,new Date()));
sqlSession.commit();
OrderDAO orderDAO = sqlSession2.getMapper(OrderDAO.lass);
orderDAO.queryOrderOfUser(1); //重新查詢資料庫
十、PageHelper
1. 使用過程
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>RELEASE</version>
</dependency>
<!--
plugins在配置文件中的位置必須符合要求,否則會報錯,順序如下:
properties?, settings?,
typeAliases?, typeHandlers?,
objectFactory?,objectWrapperFactory?,
plugins?,
environments?, databaseIdProvider?, mappers?
-->
<plugins>
<!-- com.github.pagehelper為PageHelper類所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 頁號自動回歸到合理數值 -->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
<!-- spring等價配置
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor"></bean>
</array>
</property>
</bean>
-->
//使用:
PageHelper.startPage(2,3);// 第2頁,每頁3條數據,pageNum,pageSize
PageHelper.orderBy("id desc");//可以選擇設置排序(可選)
List<User> users = mapper.queryAllUsers();//PageHelper後的第一個查詢語句,會被PageHelp增強處理(可觀測mysql日誌)
for (User user : users) {// users中已經是分頁數據
System.out.println(user);
}
//包裝一個PageInfo,其中會持有所有分頁會用到的資訊:當前頁號,每頁多少條,共多少頁,是否為第一頁/最後一頁,是否有下一頁等。
PageInfo<User> pageInfo=new PageInfo<User>(users);
PageInfo對象 概覽 |
---|
![]() |
注意:如果是多表查詢,則會有如下效果:
select t_user.id,name,gender,regist_time,
t_order.id orderId,price,note,create_time
from t_user JOIN t_order
ON t_user.id = t_order.user_id LIMIT 2, 2
#是對大表做了分頁,此時數據可能不完整,比如用戶有10個訂單,卻只能查到2個,或部分。
2. 重要提示
PageHelper.startPage
方法重要提示只有緊跟在
PageHelper.startPage
方法後的第一個Mybatis的查詢(Select)方法會被分頁。請不要配置多個分頁插件
請不要在系統中配置多個分頁插件(使用Spring時,
mybatis-config.xml
和Spring<bean>
配置方式,請選擇其中一種,不要同時配置多個分頁插件)!分頁插件不支援帶有
for update
語句的分頁對於帶有
for update
的sql,會拋出運行時異常,對於這樣的sql建議手動分頁,畢竟這樣的sql需要重視。