感謝狂神,講解清晰,以下是原視頻【狂神說Java】Mybatis最新完整教程IDEA版通俗易懂_嗶哩嗶哩_bilibili的筆記
環境:
- JDK 1.8
- Mysql 8.0+
- maven 3.6.1
- IDEA
回顧:
- JDBC
- Mysql
- Java基礎
- Maven
- Junit
SSM框架:配置文件的。(看官網文檔);
簡介
什么是Mybatis
- MyBatis 是一款優秀的持久層框架
- 它支持自定義 SQL、存儲過程以及高級映射。
- MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。
- MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)為數據庫中的記錄。
- MyBatis本是apache的一個iBatis,2010年這個項目由apache software foundation遷移到了google code,并且改名為MyBatis。2013年11月遷移到Github。
如何獲得:
- Maven倉庫
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
- Github:https://github.com/mybatis
- 中文文檔:MyBatis中文網
持久化
數據持久化
- 持久化就是將程序的數據在瞬時狀態和持久狀態轉化的過程
- 內存:斷電即失
- 數據庫(jdbc),io文件持久化
為什么需要持久化?
- 有的對象不能失去
- 內存貴
持久層
Dao層、Sevice層、Controller層...
- 完成持久化工作的代碼塊
- 層界限十分明顯
為什么需要Mybatis?
- 把數據存到數據庫中
- 方便
- 傳統的JDBC代碼太復雜。簡化、框架、自動化
- 不用Mybatis也可以。
優點:
- 解除SQL和程序代碼的耦合
- 提供映射標簽,支持對象與數據庫的orm字段關系映射
- 提供對象關系映射標簽,支持對象關系組件維護
- 提供xml標簽,支持編寫動態sql
第一個Mybatis程序
思路:搭建環境->導入Mybatis->編寫代碼->測試。
搭建環境
搭建數據庫
CREATE TABLE user (
id INT(20) NOT NULL,
name VARCHAR(30) DEFAULT NULL,
pwd VARCHAR(30) DEFAULT NULL,
PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO USER (id, name, pwd) VALUES
(1, '狂神', '123456'),
(2, '張三', '123456'),
(3, '李四', '123890')
新建項目
- 新建一個普通的maven項目
- 刪除src目錄
- 導入maven依賴(版本自定)
<!-- 導入依賴 -->
<dependencies>
<!--Mysql-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--Mybatis-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--Junit-->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
編寫mybatis工具類:
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
String resource = "mybatis-config.xml";
// InputStream inputStream = null;
try (InputStream inputStream = Resources.getResourceAsStream(resource);){
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
// 通過factory獲得sqlsession
public static SqlSession getSqlSession() {
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
目錄結構:
mybatis配置、用戶、用戶Dao以及用戶Mapper:mybatis-config.xml、User、UserDao、UserMapper.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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC"/>
<property name="username" value="**你自己的用戶名**"/>
<property name="password" value="你自己的用戶密碼"/>
</dataSource>
</environment>
</environments>
<!--Mapper.XML-->
<mappers>
<mapper resource="com/dao/UserMapper.xml"/>
</mappers>
</configuration>
其中,serverTimezone是針對MySQL8的設置。
public class User {
private int id;
private String name;
private String pwd;
public User() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}';
}
}
public interface UserDao {
List<User> getUserList();
}
<?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.dao.UserDao">
<!--select 用戶-->
<select id="getUserList" resultType="com.pojo.User">
select * from mybatis.user
</select>
</mapper>
然后編寫一個測試類:
public class UserDaoTest {
@Test
public void test() {
// 得到Session
SqlSession sqlSession = MybatisUtils.getSqlSession();
// form-1:getMapper
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
}
注意1:此時,會報出找不到xml文件的錯誤,主要是由于maven導致的問題,應該在pom.xml的配置文件下添加如下語句:
<!--約定大于配置,需要配置maven導出文件-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
注意2:如果出現“ UTF-8 序列的字節 2 無效。”是因為項目的默認配置編碼不是UTF-8。解決方法是:將IDEA設置為UTF-8編碼。或者將中文注釋的字符去掉。
最終輸出為:
User{id=1, name='狂神', pwd='123456'}
User{id=2, name='張三', pwd='123456'}
User{id=3, name='李四', pwd='123890'}
總結:編寫Mybatis工具類->寫Mybatis配置->寫實體類->寫實體類Mapper
問題:1、注釋帶中文字符可能會報錯;2、Maven約定大于配置,可能存在找不到配置文件的情況,此時要對Maven進行配置;3、mysql8和mysql5配置存在差異,體現在mybatis的xml配置文件中。
繼續深入其他語句(CRUD)
Namespace要對應到Dao;
Select選擇查詢語句
- id:對應namespace的方法名
- resultType:sql返回值
- parameterType:參數類型
<select id="getUserById" parameterType="int" resultType="com.pojo.User">
select * from mybatis.user where id = #{id}
</select>
- Insert
<insert id="insertUser" parameterType="com.pojo.User">
insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd});
</insert>
- update
<update id="updateUser" parameterType="com.pojo.User">
update mybatis.user set name=#{name}, pwd=#{pwd} where id = #{id};
</update>
- delete
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id};
</delete>
總結:編寫接口->編寫對應的mapper里的sql語句->測試(增刪改需要提交事務)
- 萬能Map
<insert id="addUser" parameterType="map">
insert into mybatis.user (id, name, pwd) values (#{userId}, #{userName}, #{passWord});
</insert>
- 模糊查詢
<select id="getUserLike" resultType="com.pojo.User">
select * from mybatis.user where name like "%" #{value} "%";
</select>
總結:
- map傳遞使用參數為Key名
- Object傳遞使用參數為屬性名
- 單個基本類型參數可以直接在sql取到
- 多個參數用map或者注解
- 通配符盡可能固定,需要考慮到注入問題。
配置解析
1、 核心配置文件
- mybatis-config.xml
- MyBatis 的配置文件包含了會深深影響 MyBatis 行為的設置和屬性信息。
- configuration(配置)
- properties(屬性)
- settings(設置)
- typeAliases(類型別名)
- typeHandlers(類型處理器)
- objectFactory(對象工廠)
- plugins(插件)
-
environments(環境配置)
- environment(環境變量)
- transactionManager(事務管理器)
- dataSource(數據源)
- environment(環境變量)
- databaseIdProvider(數據庫廠商標識)
- mappers(映射器)
重點在:properties、environments、mappers等。
- transactionManager默認為JDBC
- dataSource默認為POOLED
- 學會如何配置多套environment
- properties可以動態替換(使用外部的配置文件比如"db.properties")
- typeAliases可以給實體類起別名(可以指定包名,找到對應的Beans)
- Settings里面"logImpl",“useGeneratedKeys”,“mapUnderscoreToCamelCase”等需要了解。
- Others
- typeHandlers(類型處理器)
- objectFactory(對象工廠)
-
plugins(插件)
- MyBatis-generator-core
- MyBatis-plus
- 通用Mapper
配置文件里面可以使用properties來引入外部文件里的屬性:(配置的位置需要在Configuration里面的最開始,不然會報錯)
<properties resource="db.properties">
<property name="" value=""/>
</properties>
db.properties的內容(即屬性)。(原本的&用&代替,不然會報錯)
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC
username=你自己的用戶名
password=你自己的密碼
typeAliases(別名)的使用
直接指定別名:(實體類少時,推薦使用)
<typeAliases>
<typeAlias type="com.pojo.User" alias="User"/>
</typeAliases>
指定包名讓MyBatis自己去找對應的:(實體類多時,推薦使用)
<typeAliases>
<package name="com.pojo"/>
</typeAliases>
或者在User里面使用注解:
@Alias("User")
public class User
就可以使用User來代替之前的com.pojo.User
<select id="getUserList" resultType="User">
select * from mybatis.user
</select>
Mappers
用class來配置mapper時,接口和配置文件要同名且在同一個包下,用package配置同理。以下是錯誤示范:
<mappers>
<mapper class="com.dao.UserDao"/>
</mappers>
解決:應該將UserDao重命名為UserMapper。
最終,可以通過三種方式來配置,任選其一都可以測試成功:
<mappers>
<mapper resource="com/dao/UserMapper.xml"/>
<mapper class="com.dao.UserMapper"/>
<package name="com.dao"/>
</mappers>
作用域和生命周期
- 一旦創建了 SqlSessionFactory,就不再需要SqlSessionFactoryBuilder了。(只用一次)
- SqlSessionFactory 一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建另一個實例。(單例)
- 每個線程都應該有它自己的 SqlSession 實例。SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。(用完關閉,如下所示:)
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的應用邏輯代碼
}
ResultMap(解決對象屬性名和數據表字段名不一致的問題)詳見:XML 映射器_MyBatis中文網
比如User此時為:
public class User {
private int id;
private String name;
private String password;
屬性password與數據庫中pwd不同名,此時查詢出來的結果如下。
User{id=1, name='狂神', password='null'}
User{id=2, name='張三', password='null'}
User{id=3, name='李四', password='null'}
可見,丟失了最后一個字段,有兩種解決方法。
別名(修改查詢語句):
select id, name, pwd as password from mybatis.user
ResultMap(對外部 resultMap 的命名引用。結果映射是 MyBatis 最強大的特性,如果你對其理解透徹,許多復雜的映射問題都能迎刃而解。 resultType 和 resultMap 之間只能同時使用一個。):設定一個UserMap的resultMap,類型為User。然后,指定方法的resultMap為UserMap,即可完成映射。
<resultMap id="UserMap" type="User">
<result column="id" property="id"/>
<result column="pwd" property="password"/>
<result column="name" property="name"/>
</resultMap>
<select id="getUserList" resultMap="UserMap">
select * from mybatis.user
</select>
總結:
- ResultMap 元素是 MyBatis 中最重要最強大的元素。
- ResultMap 的設計思想是,對簡單的語句做到零配置,對于復雜一點的語句,只需要描述語句之間的關系就行了。
- ResultMap 的優秀之處——你完全可以不用顯式地配置它們。
日志
日志工廠
異常排錯:sout、debug、日志工廠
使用logImpl,指定 MyBatis 所用日志的具體實現,未指定時將自動查找。可選為:SLF4J(掌握?) | LOG4J(出Bug,掌握) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING(掌握) | NO_LOGGING。默認無。
對mybatis-config.xml進行配置,注意settings所在的順序位置。比如下面的命令行LOG。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
Log4j
是什么(引自百度百科):Log4j是Apache的一個開源項目,通過使用Log4j,我們可以控制日志信息輸送的目的地是控制臺、文件、GUI組件,甚至是套接口服務器、NT的事件記錄器、UNIX Syslog守護進程等。我們也可以控制每一條日志的輸出格式;通過定義每一條日志信息的級別,通過一個配置文件來靈活地進行配置,精細控制日志的生成過程,而不改代碼。
怎么用:
- 先導包:
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 再配置:resources/log4j.properties,以下是一個簡單的示例:
# 將等級為DEBUG的日志信息輸出到console和file這兩個目的地,console和file的定義在下面的代碼
log4j.rootLogger=DEBUG,console,file
# 控制臺輸出的相關設置
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
# 文件輸出的相關設置
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志輸出級別
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
- 再把settings改成使用LOG4J:
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
- 使用:
static Logger logger = Logger.getLogger(userDaoTest.class); // 當前類
@Test
public void testLog4j(){ //多種日志的級別
logger.info("info:進入了testlog4j");
logger.debug("debug:進入了testlog4j");
logger.warn("warn:進入了testlog4j");
logger.error("error:進入了testlog4j");
}
- 輸出:
[dao.userDaoTest]-info:進入了testlog4j
[dao.userDaoTest]-debug:進入了testlog4j
[dao.userDaoTest]-warn:進入了testlog4j
[dao.userDaoTest]-error:進入了testlog4j
分頁
目的:減少運算數據量。
1、 LIMIT實現分頁
實現sql的相關接口。
List<User> getUserByLimit(Map<String, Integer> map);
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex}, #{endIndex};
</select>
進行測試。
@Test
public void testGetUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> parameterMap = new HashMap<>();
parameterMap.put("startIndex", 1);
parameterMap.put("endIndex", 2);
List<User> userByLimit = mapper.getUserByLimit(parameterMap);
for (User user: userByLimit) {
System.out.println(user);
}
sqlSession.close();
}
2、RowBounds實現分頁
List<User> getUserByRowBounds();
<select id="getUserByRowBounds" resultMap="UserMap">
select * from mybatis.user;
</select>
@Test
public void testGetUserByRowBounds() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
// Java層面來直接selectList
RowBounds rowBounds = new RowBounds(1, 2);
List<User> userList = sqlSession.selectList("com.dao.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
3、分頁插件
PageHelper等。
使用注解開發
面向接口編程
目的:解耦
- 個體的抽象:abstract class
- 個體的某個方面的抽象:interface
三個面向
- 面向對象:考慮對象的屬性和方法。
- 面向過程:以流程(事務)考慮實現。
- 接口設計與非接口設計:與另外兩個考慮的不是一個問題,更多的是對系統整體的架構。
注解開發
可以不配置對應mapper的xml,使用簡單的注解。
@Select("select * from user")
List<User> getUsers();
然后,綁定接口:
<!--Bind interfaces-->
<mappers>
<mapper class="com.dao.UserMapper"/>
</mappers>
@Test
public void testGetUsers() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
- 實現:反射
- 底層:動態代理
MyBatis執行流程:
加載配置->SqlSessionFactoryBuilder(構建XMLConfigBuilder->轉換Configuration)->SqlSessionFactory->(transaction事務管理->executor->sqlSession->CRUD->是否執行成功)->提交事務->關閉
繼續CRUD
增改刪(注解版)及測試:
@Insert("insert into user(id, name, pwd) values (#{id},#{name},#{password})")
int addUser(User user);
@Update("update user set name=#{name}, pwd=#{password} where id = #{id}")
int updateUser(User user);
@Delete("delete from user where id = #{id}")
int deleteUser(int id);
@Test
public void testAddUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.addUser(new User(6, "aaa", "1234555"));
if (res > 0){
System.out.println("插入成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void testUpdateUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.updateUser(new User(6, "aaa", "6666"));
if (res > 0){
System.out.println("改變成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void testDeleteUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.deleteUser(6);
if (res > 0){
System.out.println("刪除成功");
}
sqlSession.commit();
sqlSession.close();
}
關于@Param注解
- String或者基本類的需要加上
- 引用類型不需要加
- 只有一個基本類型可以忽略
- SQL中引用的就是@Param中的屬性名
#{}和${}
- $直接拼接,無法防止注入
- $多用在傳入數據庫參數時
- 盡量使用#
Lombok!
是什么:Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
- 通過IDEA進行插件安裝
- 導包
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
<scope>provided</scope>
</dependency>
</dependencies>
Lombok注解內容:
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)
- @Data:無參構造,get,set,toString,hashcode,equals
- @AllArgsConstructor:有參構造
- @NoArgsConstructor:無參構造
- @Getter
- @Setter
- @ToString
復雜查詢環境
- 多個學生對應/關聯一個老師(多對一)
- 集合,一個老師有很多學生(一對多)
先建表搭建環境:
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老師');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小紅', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小張', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
流程:導入lombok->建立實體類->建立Mapper接口->建立Mapper.xml文件->在核心配置文件中綁定Mapper->測試查詢是否成功。
多對一
按照查詢嵌套處理
查找學生的時候,通過外鍵查詢對應的老師。
<select id="getAllStudents" resultMap="StudentTeacher">
select * from student;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{tid};
</select>
按照結果嵌套處理
<select id="getAllStudents2" resultMap="StudentTeacher2">
select s.id sid, s.name sname, t.id tid, t.name tname
from student s, teacher t
where s.tid = t.id;
</select>
<resultMap id="StudentTeacher2" type="Student">
<result column="sid" property="id"/>
<result column="sname" property="name"/>
<association property="teacher" javaType="Teacher">
<result column="tid" property="id"/>
<result column="tname" property="name"/>
</association>
</resultMap>
一對多
按照結果嵌套處理
<select id="getTeacherById" resultMap="TeacherStudent" parameterType="int">
select t.id tid, t.name tname, s.id sid, s.name sname
from teacher t, student s
where t.id = #{tid} and t.id = s.tid;
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result column="tname" property="name"/>
<result column="tid" property="id"/>
<collection property="students" ofType="Student">
<result column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="tid" property="tid"/>
</collection>
</resultMap>
按照查詢嵌套處理
<select id="getTeacherById2" resultMap="TeacherStudent2">
select * from teacher where id = #{tid};
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudent" column="id"/>
</resultMap>
<select id="getStudent" resultType="Student">
select * from student where tid = #{tid};
</select>
總結:
- 保證SQL可讀性
- 一對多和多對一的字段和屬性名的對應問題
- 問題如果不好排查,使用日志系統,如:Log4j
- 最終SQL優化的方向為:學習和理解Mysql引擎、InnoDB底層原理、索引、索引優化!
動態SQL
是什么:根據不同條件生成SQL語句。
搭建環境:
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客標題',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '創建時間',
`views` INT(30) NOT NULL COMMENT '瀏覽量'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
@Test
public void testAddBlogs() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDutils.getID());
blog.setAuthor("KyoDante");
blog.setCreateTime(Timestamp.from(Instant.now()));
blog.setTitle("Mybatis如此簡單");
blog.setViews(7777);
mapper.addBlog(blog);
blog.setId(IDutils.getID());
blog.setTitle("Spring如此簡單");
blog.setCreateTime(Timestamp.from(Instant.now()));
mapper.addBlog(blog);
blog.setId(IDutils.getID());
blog.setTitle("Java如此簡單");
blog.setCreateTime(Timestamp.from(Instant.now()));
mapper.addBlog(blog);
blog.setId(IDutils.getID());
blog.setTitle("微服務如此簡單");
blog.setCreateTime(Timestamp.from(Instant.now()));
mapper.addBlog(blog);
sqlSession.commit();
sqlSession.close();
}
public class IDutils {
public static String getID() {
return UUID.randomUUID().toString().replace("-", "");
}
}
IF
<select id="queryBlogIf" resultType="Blog" parameterType="map">
select * from blog where true
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
常用標簽
where和if搭配(只要滿足就填入SQL)
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
Choose(類似Switch,多選一)
<select id="queryBlogChooseWhen" parameterType="map" resultType="Blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
Trim
與 set 元素等價的自定義 trim 元素:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
sql和include
- 用sql抽取復用的部分
<sql id="if-title-author">
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
- 用include將該片段包括進去
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from blog
<where>
<include refid="if-title-author"/>
</where>
</select>
FOREACH
<select id="queryBlogForeach" parameterType="map" resultType="Blog">
select * from Blog
<where>
<foreach collection="ids" item="uuid" open="(" close=")" separator="or">
id = #{uuid}
</foreach>
</where>
</select>
緩存
是什么:查詢連接數據庫,耗資源,存到某些地方,下次可以直接使用!內存->緩存(解決高并發性能問題)
為什么:減少和數據庫的交互開銷,提高系統效率
哪里使用:經常查詢且不經常改變的數據。
- Mybatis有一級緩存和二級緩存
- 默認為開啟一級緩存。(SqlSession級別的緩存;也稱為本地緩存)
- 二級緩存是手動開啟和配置,基于namespace級別的緩存
- 為了提高擴展性,定義了緩存接口Cache,可以實現它來定義二級緩存
一級緩存
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println("==================");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user);
System.out.println(user == user2);
sqlSession.close();
}
- 增刪改會刷新緩存
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
User user3 = new User();
user3.setId(2);
user3.setName("aaaa");
user3.setPwd("bbbb");
int res = mapper.updateUserById(user3);
if (res > 0) {
System.out.println("改變成功");
}
System.out.println("==================");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user);
System.out.println(user == user2);
sqlSession.close();
}
- 或者清除緩存
sqlSession.clearCache();
二級緩存
為了什么:一級緩存只在SqlSession作用域內,會話沒了,還會存在于二級緩存中。新的會話可以從二級緩存中獲得內容。不同mapper放在對應的map中。
在Mapper中設置(如果直接這么設置,需要序列化實體類,即implements Serializable)
<cache/>
在核心配置中使用(默認開啟):
<setting name="cacheEnabled" value="true"/>
- 最開始是一級緩存
- 只有在關閉會話,或者提交會話的時候,才會轉到二級緩存
緩存原理
提高查詢效率:
從用戶的角度:先查二級再查一級。
從程序的角度:先從SqlSession,也即一級緩存;再到Mapper,也即二級緩存。
EhCache
- 導包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
- 配置resources/ehcache.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
使用
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
Redis:K-V來實現緩存。(現在常用)