1. MyBatis簡介
- MyBatis提供了數(shù)據(jù)庫查詢的自動對象綁定功能;SQL寫在XML中,便于統(tǒng)一管理和優(yōu)化,解除了SQL與代碼的耦合;執(zhí)行效率比Hibernate快;對于復雜SQL,使用MyBatis會更加靈活。但關聯(lián)的表或字段較多時,SQL工作量很大;而且SQL依賴于數(shù)據(jù)庫,導致數(shù)據(jù)庫移植性較差。
- Hibernate是全自動,Hibernate完全可以通過對象關系模型實現(xiàn)對數(shù)據(jù)庫的操作,擁有完整的JavaBean對象與數(shù)據(jù)庫的映射結構來自動生成SQL;對于單表的CRUD,使用Hibernate開發(fā)更加高效。但學習和精通的門檻較高,而且由于是自動生成SQL,因此無法直接維護SQL。
- MyBatis官網(wǎng):https://mybatis.org/mybatis-3/
- MyBatis作用:
- 減少JDBC的重復代碼
- 使數(shù)據(jù)庫環(huán)境信息的硬編碼和執(zhí)行的SQL語句的硬編碼解耦
- MyBatis分頁插件PageHelper網(wǎng)址:https://pagehelper.github.io/
2. MyBatis基本使用
以查詢user表中所有user為例
2.1 創(chuàng)建Maven工程,添加坐標
<dependencies>
<!--添加坐標-->
<!--junit單元測試-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--lombok的依賴-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<!--mysql驅(qū)動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--mybatis的依賴-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
</dependencies>
2.2 創(chuàng)建POJO
-- 創(chuàng)建user表
CREATE TABLE user(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT "id",
username VARCHAR(255) NOT NULL DEFAULT "" COMMENT "用戶名",
password VARCHAR(16) NOT NULL DEFAULT "123456" COMMENT "密碼",
balance DOUBLE(16, 3) NOT NULL DEFAULT 0.0 COMMENT "余額"
)ENGINE=INNODB, CHARSET=utf8, COMMENT="用戶表";
-- 添加用戶
INSERT INTO user VALUES
(NULL, 'user1', 'password1', 1000),
(NULL, 'user2', 'password2', 2000),
(NULL, 'user3', 'password3', 3000);
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private Integer id;
private String username;
private String password;
private Double balance;
}
2.3 創(chuàng)建PojoDao接口
原則:MyBatis的所有PojoDao接口,方法參數(shù)只設置一個;
由于是全類名+方法名的保存和尋找策略,因此PojoDao接口里的方法不能重載
PojoDao接口的工作原理是JDK動態(tài)代理
public interface UserDao {
void addUser(User user);
void deleteById(Integer id);
void updateUser(User user);
List<User> findAll();
}
2.4 創(chuàng)建外部jdbc配置文件
在resources下創(chuàng)建jdbc.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db01?useSSL=false
jdbc.username=root
jdbc.password=123456
2.5 創(chuàng)建MyBatis核心配置文件*
- 存儲在resources根路徑下
- 文件名隨意(如:SqlMapConfig.xml)
- 需要引入約束
核心配置文件的順序:
properties:引入外部properties文件
settings:全局配置參數(shù)
-
typeAliases:給類型設置別名
核心配置文件:typeAliases定義單個別名 <typeAliases> <typeAlias type="com.liu2m.pojo.User" alias="User"></typeAlias> </typeAliases> 核心配置文件:typeAliases批量定義別名 <typeAliases> <package name="com.liu2m.pojo"/> </typeAliases> 修改PojoDao.xml: <select id="findAll" resultType="User"> select * from user </select> typeHandlers:類型處理器
objectFactory:對象工廠
objectWrapperFactory
reflectorFactory
plugins:插件
environments:環(huán)境配置
databaseIdProvider:數(shù)據(jù)庫廠商標識
mappers:映射器
<?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文件-->
<properties resource="jdbc.properties"></properties>
<!--給類型設置別名-->
<typeAliases>
<package name="com.liu2m.pojo"/>
</typeAliases>
<!--配置連接數(shù)據(jù)庫的環(huán)境
default:指定使用哪一個環(huán)境-->
<environments default="dev">
<!--一個連接數(shù)據(jù)庫的環(huán)境
id:環(huán)境變量名-->
<environment id="dev">
<!--配置事務管理者
MyBatis事務使用的是jdbc-->
<transactionManager type="JDBC"></transactionManager>
<!--配置連接池
UNPOOLED:不使用連接池
POOLED:使用mybatis內(nèi)置的連接池-->
<dataSource type="POOLED">
<!--value值可以直接寫,也可以通過${}設置
${}中寫properties引入的文件中的屬性名-->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db01?useSSL=false&characterEncoding=UTF8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--加載所有的映射文件-->
<mappers>
<!--引入一個映射文件-->
<!--方式1:通過resource屬性引入映射文件的路徑-->
<!--<mapper resource="com/liu2m/dao/UserDao.xml"/>-->
<!--方式2:掃描接口-->
<!--配置單個接口-->
<!--<mapper class="com.liu2m.dao.UserDao"></mapper>-->
<!--批量配置-->
<package name="com.liu2m.dao"/>
</mappers>
</configuration>
2.6 創(chuàng)建SQL映射文件*
- 存儲在resources里面
- 全路徑和文件名要和相應的PojoDao接口的相同(在resources里創(chuàng)建多級文件夾時用/分隔,而不是.)
- 需要引入約束
根標簽mapper的namespace屬性的值為對應的PojoDao接口的全限定名
根標簽mapper下的每一個子標簽對應PojoDao接口中的一個方法:
- 查詢方法對應select標簽
- 添加方法對應insert標簽
- 刪除方法對應delete標簽
- 修改方法對應update標簽
子標簽的id對應方法的名字
子標簽的parameterType對應方法的形參類型
子標簽的resultType(只有select標簽有)對應方法的返回值類型:
- 如果返回值類型是簡單類型,則直接寫對應的Java類型
- 如果返回值類型是POJO、Map,則直接寫POJO的全限定名
- 如果返回值類型是Collection,則resultType是集合中元素的類型
- 結果集的字段名和POJO的屬性名能夠?qū)獣r,resultType才可以自動映射
使用參數(shù)中的數(shù)據(jù):
- 如果參數(shù)為pojo、map,則通過#{屬性名或Map的key}來引入對應的值
- 如果參數(shù)為基本類型數(shù)據(jù),則通過#{任意字符串}來引入對應的值
- 如果參數(shù)為pojo包含pojo,如ResultBean中包含User,則使用#{user.username}
resultMap標簽:解決查詢出來的結果的列名和JavaBean屬性不一致的情況
- id屬性:映射規(guī)則的唯一id
- type屬性:將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.liu2m.dao.UserDao">
<resultMap id="findAllMap" type="User">
<!--id:查詢結果集的唯一標識;如果唯一約束是多個字段段,則定義多個id-->
<id property="id" column="u_id"></id>
<!--
property:指定的POJO類的屬性
column:sql查詢出來的字段名-->
<result property="username" column="u_username"></result>
<result property="password" column="u_password"></result>
<result property="balance" column="u_balance"></result>
</resultMap>
<select id="findAll" resultMap="findAllMap">
select id as u_id, username as u_username, password as u_password, balance as u_balance from user
</select>
<!--selectKey可以獲取主鍵,存儲在對應的POJO對象中
keyColumn:要查詢的字段名
keyProperty:將查詢到的結果賦值到哪個屬性
resultType:查詢到的結果的類型
order:在執(zhí)行添加之前還是之后執(zhí)行selectKey里的SQL-->
<insert id="addUser" parameterType="com.liu2m.pojo.User">
<selectKey keyColumn="id" keyProperty="id" resultType="int" order="AFTER">
select LAST_INSERT_ID()
</selectKey>
insert into user values (null, #{username}, #{password}, #{balance})
</insert>
<update id="updateUser" parameterType="com.liu2m.pojo.User">
update user set username = #{username}, password = #{password}, balance = #{balance}
</update>
<delete id="deleteById" parameterType="java.lang.Integer">
delete from user where id = #{id}
</delete>
</mapper>
2.7 單元測試
public class UserDaoTest {
InputStream is;
SqlSession sqlSession;
UserDao userDao;
@Before
public void init() throws IOException {
// 1. 創(chuàng)建SqlSessionFactoryBuilder對象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 讀取核心配置文件,轉換成字節(jié)輸入流
is = Resources.getResourceAsStream("SqlMapConfig.xml");
// 3. 創(chuàng)建SqlSessionFactory對象
SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(is);
// 4. 創(chuàng)建SqlSession對象
sqlSession = sessionFactory.openSession();
// 5. 創(chuàng)建UserDao的代理對象
userDao = sqlSession.getMapper(UserDao.class);
}
@Test
public void testFindAll() {
// 6. 調(diào)用需要執(zhí)行的方法
List<User> userList = userDao.findAll();
for (User user : userList) {
System.out.println(user);
}
}
@Test
public void testAddUser() {
User user = new User(0, "user4", "654321", 4000.0);
userDao.addUser(user);
System.out.println(user.getId());
}
@After
public void destroy() throws IOException {
// 在CUD操作中,需要通過sqlSession.commit()方法來執(zhí)行提交操作
sqlSession.commit();
// 7. 釋放資源
sqlSession.close();
is.close();
}
}
2.8 #{}與${}的區(qū)別*
- 傳參的區(qū)別:
-
#{}先用?占位,然后再設置參數(shù),可以防止SQL注入 - ${}直接拼接SQL語句
-
-
#{}不能寫在引號里,而${}必須要寫在引號里 - pojo、map類型的參數(shù),#{}和${}括號里都是寫屬性名或Map的key
- 基本類型、字符串類型的參數(shù),#{}和${}括號里可以寫任意字符串
- 模糊查詢:
- "%"#{username}"%"
- concat("%", #{username}, "%")
- "%${value}%"
2.9 自定義SqlSessionFactory工具類
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class SqlSessionFactoryUtils {
private static InputStream is;
private static SqlSessionFactory sessionFactory;
static {
try {
// 1. 創(chuàng)建SqlSessionFactoryBuilder對象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 讀取核心配置文件,轉換成字節(jié)輸入流
is = Resources.getResourceAsStream("SqlMapConfig.xml");
// 3. 創(chuàng)建SqlSessionFactory對象
sessionFactory = sqlSessionFactoryBuilder.build(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 獲取一個SqlSession
*/
public static SqlSession openSession() {
SqlSession sqlSession = null;
// 4. 創(chuàng)建SqlSession對象
sqlSession = sessionFactory.openSession();
return sqlSession;
}
/**
* 提交事務并關閉資源
*/
public static void commitAndClose(SqlSession sqlSession) {
sqlSession.commit();
sqlSession.close();
}
/**
* 回滾事務并關閉資源
*/
public static void rollbackAndClose(SqlSession sqlSession) {
sqlSession.rollback();
sqlSession.close();
}
}
3. MyBatis日志
-
導入坐標:
<!--使用log4j日志打印框架打印mybatis日志--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.6</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.6</version> </dependency> -
在resources下創(chuàng)建log4j.properties文件:
#日志級別:ERROR > WARN > INFO > DEBUG log4j.rootLogger=DEBUG,stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=[%-5p] %t %l %d %rms:%m%n log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=E:\\Logs\\IdeaLogs\\IdeaLogs.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS\} %-5p [%t] {%c}-%m%n
4. Mybatis動態(tài)SQL*
動態(tài)SQL:SQL語句的主體結構,在編譯時無法確定,只有等到程序運行后,在執(zhí)行過程中才能確定
4.1 if標簽
滿足test屬性的邏輯表達式,則if標簽里面的語句有效
多條件查詢時,每次查詢的條件不確定有哪些,就可以使用if標簽
當參數(shù)值為int的0時,MyBatis會將參數(shù)值其轉化為空字符串
select * from user
<!--如果參數(shù)balance大于0,則添加balance的條件-->
<if test="balance != null and balance > 0">
where balance = #{balance}
</if>
4.2 where標簽
select * from user
<!--where標簽的作用:
如果有if條件成立,則會在SQL語句中添加WHERE關鍵字,并且還會自動去掉第一個條件的and-->
<where>
<if test="balance != null and balance > 0">
and balance = #{balance}
</if>
<if test="password != null and password.length > 6">
and password = #{password}
</if>
</where>
4.3 foreach標簽
<!--刪除所有在id集合中的行-->
delete from user
<!--
collection:要遍歷的對象;如果要遍歷的對象是一個集合,就寫list
item:遍歷出來的每一個元素
index:遍歷出來的每一個元素的下標
separator:分隔符
open:在遍歷出來的第一個元素之前拼接的字符串
close:在遍歷出來的最后一個元素之后拼接的字符串
-->
<foreach open="where id in (" close=")" collection="list" item="id" index="i" separator=", ">
#{id}
</foreach>
4.4 sql標簽和include標簽
sql標簽可以定義公共的sql片段,需要時可以用include標簽引入sql片段
<sql id="findAll">
select * from user
</sql>
<select id="findAll" resultType="User" parameterType="int">
<include refid="findAll"></include>
where id = #{id}
</select>
5. Mybatis多表關聯(lián)查詢*
建立數(shù)據(jù)庫環(huán)境:
DROP TABLE IF EXISTS user;
-- 創(chuàng)建用戶表
CREATE TABLE user(
uid INT PRIMARY KEY AUTO_INCREMENT COMMENT "id",
username VARCHAR(255) NOT NULL DEFAULT "" COMMENT "用戶名",
password VARCHAR(16) NOT NULL DEFAULT "123456" COMMENT "密碼",
balance DOUBLE(16, 3) NOT NULL DEFAULT 0.0 COMMENT "余額"
)ENGINE=INNODB, CHARSET=utf8, COMMENT="用戶表";
-- 添加用戶
INSERT INTO user VALUES
(NULL, 'user1', 'password1', 1000),
(NULL, 'user2', 'password2', 2000),
(NULL, 'user3', 'password3', 3000);
-- 創(chuàng)建賬戶表
CREATE TABLE account (
aid INT AUTO_INCREMENT PRIMARY KEY,
money DOUBLE,
uid INT,
CONSTRAINT fk_account_uid FOREIGN KEY (uid) REFERENCES user(uid)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加賬戶
INSERT INTO account VALUES
(NULL, 1000, 1),
(NULL, 2000, 1),
(NULL, 1000, 2),
(NULL, 2000, 2),
(NULL, 3000, 3);
-- 創(chuàng)建課程表
CREATE TABLE course(
cid INT PRIMARY KEY AUTO_INCREMENT,
coursename VARCHAR(255),
book VARCHAR(255)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加課程
INSERT INTO course VALUES
(NULL, '面向?qū)ο蟪绦蛟O計', 'Java編程思想'),
(NULL, '數(shù)據(jù)結構與算法', '算法導論'),
(NULL, '設計模式', '大話設計模式');
-- 創(chuàng)建選課表
CREATE TABLE course_selection(
uid INT,
cid INT,
CONSTRAINT fk_course_selection_uid FOREIGN KEY (uid) REFERENCES user(uid),
CONSTRAINT fk_course_selection_cid FOREIGN KEY (cid) REFERENCES course(cid),
PRIMARY KEY (uid, cid)
)ENGINE=INNODB, CHARSET=utf8;
-- 添加選課信息
INSERT INTO course_selection VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 1),
(2, 2),
(3, 1);
5.1 一對一
根據(jù)賬戶id查詢賬戶信息,并關聯(lián)查詢對應的用戶表信息;由于一個賬戶只能對應一個用戶,因此為一對一查詢
在外鍵所在的POJO中新增一個外鍵參考的POJO類型的屬性
-
創(chuàng)建Account表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class Account implements Serializable { private Integer aid; private Double money; private Integer uid; /** * 表示一個Account對應一個User */ private User user; } -
創(chuàng)建PojoDao接口:
public interface AccountDao { Account findAccountUserByAid(int aid); } -
創(chuàng)建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.liu2m.dao.AccountDao"> <!--使用resultMap自定義映射規(guī)則--> <resultMap id="accountUserMap" type="Account"> <id column="aid" property="aid"></id> <result column="money" property="money"></result> <result column="uid" property="uid"></result> <!--使用association標簽配置一對一的映射 property:要映射的POJO屬性的變量名 javaType:要映射的POJO屬性的類型--> <association property="user" javaType="User"> <!--result語法同上面的result--> <result column="uid" property="uid"></result> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="balance" property="balance"></result> </association> </resultMap> <select id="findAccountUserByAid" parameterType="int" resultMap="accountUserMap"> SELECT * FROM user JOIN account ON user.uid = account.uid WHERE account.aid = #{aid} </select> </mapper> -
單元測試:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); AccountDao accountDao = sqlSession.getMapper(AccountDao.class); Account account = accountDao.findAccountUserByAid(1); System.out.println(account); /** * 結果: * Account(aid=1, money=1000.0, uid=1, user=User(id=null, uid=1, username=user1, password=password1, balance=1000.0)) */ }
5.2 一對多
根據(jù)用戶id查詢用戶信息,并關聯(lián)查詢對應的所有賬戶表信息;由于一個用戶可能對應多個賬戶,因此為一對多查詢
在外鍵參考的POJO中新增一個List<外鍵所在POJO>類型的屬性
-
創(chuàng)建User表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; /** * 表示一個User對應多個Account */ private List<Account> accountList; } -
創(chuàng)建PojoDao接口:
public interface UserDao { User findUserAccountByUid(int uid); } -
創(chuàng)建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.liu2m.dao.UserDao"> <!--使用resultMap自定義映射規(guī)則--> <resultMap id="userAccountMap" type="User"> <id column="uid" property="uid"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="balance" property="balance"></result> <!--使用collection標簽配置一對多的映射 property:要映射的POJO屬性的變量名 ofType:查詢出的每一行數(shù)據(jù)映射成哪一個POJO類型--> <collection property="accountList" ofType="Account"> <!--result語法同上面的result--> <result column="aid" property="aid"></result> <result column="money" property="money"></result> <result column="uid" property="uid"></result> </collection> </resultMap> <select id="findUserAccountByUid" parameterType="int" resultMap="userAccountMap"> SELECT * FROM user JOIN account ON user.uid = account.uid WHERE user.uid = #{uid} </select> </mapper> -
單元測試:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.findUserAccountByUid(1); System.out.println(user); /** * 結果: * User(uid=1, username=user1, password=password1, balance=1000.0, * accountList=[Account(aid=1, money=1000.0, uid=1, user=null), Account(aid=2, money=2000.0, uid=1, user=null)]) */ }
5.3 多對多
根據(jù)用戶id查詢用戶所選的所有課程信息,相當于一個用戶對應多個課程
多對多關系可以看成是雙向的一對多關系
-
創(chuàng)建Course表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class Course implements Serializable { private Integer cid; private String coursename; private String book; } -
創(chuàng)建User表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; /** * 表示一個User對應多個Course */ private List<Course> courseList; } -
創(chuàng)建PojoDao接口:
public interface UserDao { User findUserCourseByUid(int uid); } -
創(chuàng)建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.liu2m.dao.UserDao"> <!--使用resultMap自定義映射規(guī)則--> <resultMap id="userCourseMap" type="User"> <id column="uid" property="uid"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="balance" property="balance"></result> <collection property="courseList" ofType="Course"> <result column="cid" property="cid"></result> <result column="coursename" property="coursename"></result> <result column="book" property="book"></result> </collection> </resultMap> <select id="findUserCourseByUid" parameterType="int" resultMap="userCourseMap"> SELECT * FROM user JOIN course_selection ON user.uid = course_selection.uid JOIN course ON course.cid = course_selection.cid WHERE user.uid = #{uid}; </select> </mapper> -
單元測試:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.findUserCourseByUid(2); System.out.println(user); /** * 結果: * User(uid=2, username=user2, password=password2, balance=2000.0, * courseList=[Course(cid=1, coursename=面向?qū)ο蟪绦蛟O計, book=Java編程思想), Course(cid=2, coursename=數(shù)據(jù)結構與算法, book=算法導論)]) */ }
6. Mybatis分步查詢延遲加載*
- 立即加載:一調(diào)用方法就立即發(fā)起查詢
- 延遲加載(懶加載):需要使用數(shù)據(jù)時,才發(fā)起查詢;如果暫時不需要使用數(shù)據(jù),則暫時不查詢
- 缺點:因為只有當需要用到數(shù)據(jù)時,才會進行數(shù)據(jù)庫查詢,這樣在大批量數(shù)據(jù)查詢時,因為查詢工作也要消耗時間,所以可能造成用戶等待時間變長,造成用戶體驗下降
- 優(yōu)點:先從單表查詢,需要時再從關聯(lián)表去關聯(lián)查詢;由于查詢單表要比關聯(lián)查詢多張表速度要快,因此可以提高數(shù)據(jù)庫性能
- 延遲加載的SQL語句類似于嵌套子查詢,分步驟查詢
-
全局延遲加載:在MyBatis核心配置文件中配置
<settings> <!--lazyLoadingEnabled=true:所有關聯(lián)對象都會延遲加載 特定關聯(lián)關系中可設置fetchType屬性來覆蓋lazyLoadingEnabled--> <setting name="lazyLoadingEnabled" value="true"/> <!--aggressiveLazyLoading=false:每個屬性會按需加載 true:對具有懶加載特性對象的任意調(diào)用都會加載該對象的所有屬性--> <setting name="aggressiveLazyLoading" value="false"/> </settings>
6.1 association標簽實現(xiàn)分步查詢延遲加載
根據(jù)賬戶id查詢賬戶信息,在需要用戶信息時再關聯(lián)查詢對應的用戶表信息
-
創(chuàng)建User表和Account表的POJO:
@NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; } @Data @NoArgsConstructor @AllArgsConstructor public class Account implements Serializable { private Integer aid; private Double money; private Integer uid; /** * 表示一個Account對應一個User */ private User user; } -
創(chuàng)建PojoDao接口:
public interface AccountDao { Account findAccountByAid(int aid); } public interface UserDao { User findUserByUid(int uid); } -
創(chuàng)建SQL映射文件AccountDao.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.liu2m.dao.AccountDao"> <resultMap id="accountUserMap" type="Account"> <id column="aid" property="aid"></id> <result column="money" property="money"></result> <result column="uid" property="uid"></result> <!--使用association標簽配置一對一映射的延遲加載 fetchType="lazy":局部延遲加載(配置了這個resultMap的查詢會延遲加載) select:調(diào)用其他的select標簽(方法) column:往調(diào)用的select標簽(方法)中傳入的參數(shù)--> <association property="user" javaType="User" fetchType="lazy" select="com.liu2m.dao.UserDao.findUserByUid" column="uid"> </association> </resultMap> <select id="findAccountByAid" parameterType="int" resultMap="accountUserMap"> SELECT * FROM account WHERE aid = #{aid} </select> </mapper> -
創(chuàng)建SQL映射文件UserDao.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.liu2m.dao.UserDao"> <select id="findUserByUid" parameterType="int" resultType="User"> SELECT * FROM user WHERE uid = #{uid} </select> </mapper> -
單元測試:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); AccountDao accountDao = sqlSession.getMapper(AccountDao.class); Account account = accountDao.findAccountByAid(1); System.out.println(account.getMoney()); System.out.println(account.getUser()); }
6.2 collection標簽實現(xiàn)分步查詢延遲加載
根據(jù)用戶id查詢用戶信息,在需要賬戶信息時再關聯(lián)查詢對應的所有賬戶表信息
-
創(chuàng)建User表和Account表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; /** * 表示一個User對應多個Account */ private List<Account> accountList; } @Data @NoArgsConstructor @AllArgsConstructor public class Account implements Serializable { private Integer aid; private Double money; private Integer uid; } -
創(chuàng)建PojoDao接口:
public interface UserDao { User findUserByUid(int uid); } public interface AccountDao { List<Account> findAccountListByUid(int uid); } -
創(chuàng)建SQL映射文件UserDao.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.liu2m.dao.UserDao"> <resultMap id="userAccountMap" type="User"> <id column="uid" property="uid"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="balance" property="balance"></result> <!--使用collection標簽配置一對多映射的延遲加載 fetchType="lazy":局部延遲加載(配置了這個resultMap的查詢會延遲加載) select:調(diào)用其他的select標簽(方法) column:往調(diào)用的select標簽(方法)中傳入的參數(shù)--> <collection property="accountList" ofType="Account" fetchType="lazy" select="com.liu2m.dao.AccountDao.findAccountListByUid" column="uid"> </collection> </resultMap> <select id="findUserByUid" parameterType="int" resultMap="userAccountMap"> SELECT * FROM user WHERE uid = #{uid} </select> </mapper> -
創(chuàng)建SQL映射文件AccountDao.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.liu2m.dao.AccountDao"> <select id="findAccountListByUid" resultType="com.liu2m.pojo.Account"> SELECT * FROM account WHERE uid = #{uid} </select> </mapper> -
單元測試:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.findUserByUid(1); System.out.println(user.getUsername()); System.out.println(user.getAccountList()); }
7. MyBatis緩存-
MyBatis緩存類別:
- 一級緩存:是sqlSession對象的緩存,自帶的(不需要配置)不可卸載的(不想使用還不行);一級緩存的生命周期與sqlSession一致
- 二級緩存:是SqlSessionFactory的緩存,只要是同一個SqlSessionFactory創(chuàng)建的SqlSession就共享二級緩存的內(nèi)容,并且可以操作二級緩存。二級緩存如果要使用,需要手動開啟(需要配置)
7.1 一級緩存
- 查詢時,先去緩存中找;如果緩存中沒有,則從數(shù)據(jù)庫查詢用戶信息,然后將查詢到的信息存儲到緩存中;如果緩存中有,則直接從緩存中獲取信息。
- 清除一級緩存:
- sqlSession.commit(); // commit時清除一級緩存是為了避免數(shù)據(jù)發(fā)生增刪改后產(chǎn)生臟讀
- sqlSession.close();
- sqlSession.clearCache();
- 執(zhí)行增刪改操作
7.2 二級緩存
-
步驟1:在MyBatis核心配置文件中開啟二級緩存(由于默認開啟,因此這一步可省略)
<!--cacheEnabled: true:開啟二級緩存(默認) false:不開啟二級緩存--> <settings> <setting name="cacheEnabled" value="true"/> </settings> -
步驟2:在要使用二級緩存的SQL映射文件的mapper標簽里增加cache標簽
<mapper namespace="com.liu2m.dao.UserDao"> <cache></cache> <select id="findAll" resultType="com.liu2m.pojo.User"> select * from user </select> </mapper> 步驟3:要使用二級緩存的POJO類必須實現(xiàn)Serializable接口
- 只有當前的sqlSession.close()時,該sqlSession的數(shù)據(jù)才會存入二級緩存中
- 執(zhí)行增刪改操作時會清除二級緩存
- select標簽屬性:
- flushCache默認為false
- useCache默認為true
- insert、delete、update標簽屬性:
- flushCache默認為true
- 沒有useCache屬性
8. MyBatis注解-
使用注解代替了SQL映射文件
@SelectKey():代替了selectKey標簽
@Insert("SQL"):代替了insert標簽
@Delete("SQL"):代替了delete標簽
@Update("SQL"):代替了update標簽
@Select("SQL"):代替了select標簽
@Results(id = "", value = @Result())
或@Results(@Result())
或@Results({@Result(), @Result()})
@Result(column = "列名", property = "屬性名", one = @One(select = "指定用來多表查詢的sqlmapper"), many = @Many(select = ""))
@Results代替了resultMap標簽
@Result代替了result標簽和id標簽
@One()代替了association標簽
@Many()代替了collection標簽
8.1 MyBatis注解實現(xiàn)基本CRUD
-
創(chuàng)建PojoDao接口:
public interface UserDao { @SelectKey(keyProperty = "uid", keyColumn = "uid", before = false, statement = "select last_insert_id()", resultType = int.class) @Insert("INSERT INTO user VALUES (NULL, #{username}, #{password}, #{balance})") void addUser(User user); @Delete("delete from user where uid = #{id}") void deleteById(Integer id); @Update("update user set username = #{username}, password = #{password}, balance = #{balance} where uid = #{uid}") void updateUser(User user); @Select("select * from user") List<User> findAll(); } 創(chuàng)建MyBatis核心配置文件
-
單元測試:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = new User(2, "user4", "password4", 4000.0); userDao.addUser(user); System.out.println(user.getUid()); userDao.deleteById(3); userDao.updateUser(user); userDao.findAll().stream().forEach(System.out::println); SqlSessionFactoryUtils.commitAndClose(sqlSession); }
8.2 MyBatis注解實現(xiàn)一對一映射及延遲加載
-
創(chuàng)建Account表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class Account implements Serializable { private Integer aid; private Double money; private Integer uid; /** * 表示一個Account對應一個User */ private User user; } -
創(chuàng)建PojoDao接口:
public interface UserDao { @Select("select * from user where uid = #{id}") User findUserByUid(int uid); } public interface AccountDao { @Results(id = "accountUserMap", value = { @Result(column = "uid", property = "uid"), @Result(property = "user", one = @One(select = "com.liu2m.dao.UserDao.findUserByUid", fetchType = FetchType.LAZY), column = "uid") }) @Select("select * from account where aid = #{aid}") Account findAccountByAid(int aid); } -
單元測試:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); AccountDao accountDao = sqlSession.getMapper(AccountDao.class); Account account = accountDao.findAccountByAid(1); System.out.println(account.getMoney()); }
8.3 MyBatis注解實現(xiàn)一對多映射及延遲加載
-
創(chuàng)建User表的POJO:
@Data @NoArgsConstructor @AllArgsConstructor public class User implements Serializable { private Integer uid; private String username; private String password; private Double balance; /** * 表示一個User對應多個Account */ private List<Account> accountList; } -
創(chuàng)建PojoDao接口:
public interface UserDao { @Results(id = "userAccountMap", value = { @Result(column = "uid", property = "uid", id = true), @Result(property = "accountList", many = @Many(select = "com.liu2m.dao.AccountDao.findAccountListByUid", fetchType = FetchType.LAZY), column = "uid") }) @Select("SELECT * FROM user WHERE uid = #{uid}") User findUserByUid(int uid); } public interface AccountDao { @Select("SELECT * FROM account WHERE uid = #{uid}") List<Account> findAccountListByUid(int uid); } -
單元測試:
@Test public void test01() { SqlSession sqlSession = SqlSessionFactoryUtils.openSession(); UserDao userDao = sqlSession.getMapper(UserDao.class); User user = userDao.findUserByUid(1); System.out.println(user); }