狂神Mybatis筆記
Mybatis
所有程式碼已上傳Gitee
https://gitee.com/deza-to/learn-mybatis
1、簡介
1.1、什麼是Mybatis
- MyBatis 是一款優秀的持久層框架。
- 它支援自定義 SQL、儲存過程以及高階對映。
- MyBatis 免除了幾乎所有的 JDBC 程式碼以及設定引數和獲取結果集的工作。
- MyBatis 可以通過簡單的 XML 或註解來配置和對映原始型別、介面和 Java POJO(Plain Old Java Objects,普通老式 Java 物件)為資料庫中的記錄。
- MyBatis 本是apache的一個開源專案iBatis, 2010年這個專案由apache software foundation 遷移到了[google code](
如何獲取Mybatis
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency>
- Github :
- 中文文件 :
1.2、持久化
資料持久化
- 持久化就是將程式的資料在持久狀態和瞬時狀態轉換的過程
- 記憶體:斷電即失
- 資料庫(jdbc),io檔案持久化
1.3、持久層
Dao層,Service層,Controller層
- 完成持久化工作的程式碼塊
- 層界限十分明顯
1.4、為什麼需要Mybatis
-
方便
-
傳統的JDBC程式碼太複雜,需要框架來簡化。自動化。
-
幫助程式設計師將資料存入到資料庫中。
-
不用Mybatis也可以,但是使用了更容易上手。
-
優點:
- 簡單易學
- 靈活
- sql和程式碼的分離,提高了可維護性
- 提供對映標籤,支援物件與資料庫的orm欄位關係對映
- 提供物件關係對映標籤,支援物件關係組建維護
- 提供xml標籤,支援編寫動態sql
2、第一個Mybatis程式
思路:搭建環境->匯入Mybatis->編寫程式碼->測試
2.1、搭建環境
新建專案
-
新建一個普通的maven專案
-
刪除src目錄
-
匯入maven依賴
<dependencies> <!--mysql驅動--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
2.2、建立一個模組
-
編寫Mybatis核心配置檔案
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--configuration核心配置檔案--> <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=UTF-8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="cqp123"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
-
編寫Mybatis工具類
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 MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { //使用Mybatis第一步:獲取sqlSessionFactory物件 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //既然有了 SqlSessionFactory,顧名思義,我們可以從中獲得 SqlSession 的例項。SqlSession 提供了在資料庫執行 SQL 命令所需的所有方法。 //你可以通過 SqlSession 例項來直接執行已對映的 SQL 語句。 public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
2.3、編寫程式碼
-
實體類
package com.chen.pojo; import java.util.Date; public class User { private int id; private String name; private String pwd; private Date createTime; 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; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public User() { } public User(int id, String name, String pwd, Date createTime) { this.id = id; this.name = name; this.pwd = pwd; this.createTime = createTime; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + ", createTime=" + createTime + '}'; } }
-
Dao介面
import com.chen.pojo.User; import java.util.List; public interface UserDao { List<User> getUserList(); }
-
Dao介面實現類
<?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"> <!--namespace=繫結一個對應的Dao/Mapper介面--> <mapper namespace="com.chen.dao.UserDao"> <select id="getUserList" resultType="com.chen.pojo.User"> select * from user; </select> </mapper>
2.4、測試
import com.chen.pojo.User;
import com.chen.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
@Test
public void test1(){
//第一步:獲取sqlSession物件
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方式一:getMapper
UserDao mapper = sqlSession.getMapper(UserDao.class);
List<User> userList = mapper.getUserList();
userList.stream().forEach(System.out::println);
//方式二:
//List<User> userList = sqlSession.selectList("com.chen.dao.UserDao.getUserList");
//關閉sqlSession
sqlSession.close();
}
}
可能遇到的問題:
- 配置檔案沒有註冊
- 繫結介面錯誤
- 方法名不對
- 返回型別不對
- Maven匯出資源問題
3、CRUD
3.1、namespace
namespace中的包名要和介面的包名一致
3.2、select
選擇,查詢語句
- id:就是對應的namespace中的方法名
- resultType:sql語句執行的返回值
- parameterType:引數型別
- 編寫介面
//根據id查詢使用者
User getUserById(int id);
- 編寫對應的mapper中的sql語句
<select id="getUserById" resultType="com.chen.pojo.User" parameterType="int">
select * from mybatis.user where id = #{id};
</select>
- 測試
@Test
public void testGetUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
3.3、insert
- 編寫介面
//插入一個使用者
int addUser(User user);
- 編寫對應的mapper中的sql語句
<!--物件中的屬性可以直接取出來-->
<insert id="addUser" parameterType="com.chen.pojo.User">
insert into mybatis.user (name,pwd,createTime) values(#{name},#{pwd},#{createTime});
</insert>
- 測試
@Test
public void testAddUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setName("李雲龍");
user.setPwd("123abc");
user.setCreateTime(new Date());
int i = mapper.addUser(user);
sqlSession.commit();
System.out.println("插入了"+i+"個使用者");
sqlSession.close();
}
3.4、update
- 編寫介面
//修改使用者
int updateUser(User user);
- 編寫對應的mapper中的sql語句
<update id="updateUser" parameterType="com.chen.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd},createTime=#{createTime} where id=#{id};
</update>
- 測試
@Test
public void testUpdateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User("楚雲飛","eeeeee",new Date());
user.setId(3);
int i = mapper.updateUser(user);
System.out.println("更新了"+i+"個使用者");
sqlSession.commit();
sqlSession.close();
}
3.5、delete
- 編寫介面
//刪除使用者
int deleteUser(int id);
- 編寫對應的mapper中的sql語句
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id};
</delete>
- 測試
@Test
public void testDeleteUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(4);
System.out.println("刪除了"+i+"個使用者");
sqlSession.commit();
sqlSession.close();
}
注意點:
增刪改需要提交事務
3.6、分析錯誤
-
標籤不要匹配錯
-
resource繫結mapper,需要使用路徑
-
程式配置檔案必須符合規範
-
NullPointException,沒有註冊到資源
-
輸出的xml檔案中存在中文亂碼問題
-
maven資源沒有匯出=>pom.xml中配置
3.7、萬能的Map方法
假設,我們的實體類或者資料庫中的表,欄位或者引數過多,我們應當考慮使用Map。
//萬能的Map
int addUser2(Map<String,Object> map);
<!--物件中的屬性可以直接取出來 傳遞map中的key-->
<insert id="addUser2" parameterType="map">
insert into mybatis.user (name,pwd,createTime) values(#{userName},#{userPwd},#{createTime});
</insert>
@Test
public void testAddUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("userName","lemon");
map.put("userPwd","lemontree");
map.put("createTime",new Date());
int i = mapper.addUser2(map);
sqlSession.commit();
System.out.println("插入了"+i+"個使用者");
sqlSession.close();
}
Map傳遞引數,直接在sql中取出key即可。【parameterType="map"】
物件傳遞引數,直接在sql中取物件的屬性即可。【parameterType="Object"】
只有一個基本型別引數的情況下,可以直接在sql中取到。
多個引數用Map,或者註解
3.8、模糊查詢
-
Java程式碼執行的時候,傳遞萬用字元% %
List<User> userList = mapper.getUserLike(%李%);
-
在sql拼接中使用萬用字元
select * from mybatis.user where name like "%"#{value}"%";
4、配置解析
4.1、核心配置檔案
-
mybatis-config.xml
-
MyBatis 的配置檔案包含了會深深影響 MyBatis 行為的設定和屬性資訊。
configuration(配置) properties(屬性) settings(設定) typeAliases(類型別名) typeHandlers(型別處理器) objectFactory(物件工廠) plugins(外掛) environments(環境配置) environment(環境變數) transactionManager(事務管理器) dataSource(資料來源) databaseIdProvider(資料庫廠商標識) mappers(對映器)
4.2、環境配置(environments)
Mybatis可以配置成適應多種環境
不過要記住:儘管可以配置多個環境,但每個 SqlSessionFactory 例項只能選擇一種環境。
<environments default="test">
<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=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="cqp123"/>
</dataSource>
</environment>
<environment id="test">
<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=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="cqp123"/>
</dataSource>
</environment>
</environments>
Mybatis預設的事務管理器就是JDBC,連線池:POOLED
4.3、屬性(properties)
我們可以通過properties屬性來實現引用配置檔案
編寫一個配置檔案
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username=root
password=cqp123
在核心配置檔案中引入
**在xml中所有標籤都有固定的順序 **
<properties resource="db.properties"/>
- 可以直接引入外部檔案
- 可以在其中增加一些屬性配置
- 如果兩個檔案有同一個欄位,優先使用外部配置檔案的
4.4、類型別名(typeAliases)
- 類型別名可為 Java 型別設定一個縮寫名字。
- 它僅用於 XML 配置,意在降低冗餘的全限定類名書寫
<!--給實體類起別名-->
<typeAliases>
<typeAlias type="com.chen.pojo.User" alias="User"/>
</typeAliases>
也可以指定一個包名,Mybatis會在包名下面搜尋需要的Java Bean,
掃描實體類的包,它的預設別名就為這個類的類名,首字母小寫(大寫也可以)
<typeAliases>
<package name="com.chen.pojo"/>
</typeAliases>
在實體類比較少的時候使用第一種方式,
如果實體類十分多,建議使用第二種。
第一中可以自定義別名,第二種不行,如果非要改,需要在實體類上增加註解
@Alias("User")
public class User {
private int id;
private String name;
private String pwd;
private Date createTime;
...
4.5、屬性
<setting>
<!--快取-->
<setting name="cacheEnabled" value="true"/>
<!--懶載入-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--日誌型別-->
<setting name="logImpl"/>
</setting>
logImpl | 指定 MyBatis 所用日誌的具體實現,未指定時將自動查詢。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING |
---|---|---|
4.6、對映器(mappers)
MapperRegistry:註冊繫結我們的Mapper檔案
方式一:【推薦使用】
<!--每一個Mapper.cml都需要在Mybatis核心配置檔案中註冊-->
<mappers>
<mapper resource="com/chen/dao/UserMapper.xml"/>
</mappers>
方式二:
<mappers>
<mapper class="com.chen.dao.UserMapper"/>
</mappers>
注意點:
- 介面和他的Mapper配置檔案必須同名
- 介面和他的Mapper配置檔案必須在同一個包下
方式三:
使用掃描包進行注入
<mappers>
<package name="com.chen.dao"/>
</mappers>
注意點:
- 介面和他的Mapper配置檔案必須同名
- 介面和他的Mapper配置檔案必須在同一個包下
4.6、生命週期和作用域
生命週期和作用域是至關重要的,因為錯誤的使用會導致非常嚴重的併發問題
SqlSessionFactoryBuilder:
- 一旦建立了SqlSessionFactory,就不再需要它了。
- 適合被定義為區域性變數
SqlSessionFactory:
- 可以想象為資料庫連線池。
- SqlSessionFactory一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立另一個例項。
- SqlSeesionFactory的最佳作用域是應用作用域。
- 最簡單的就是使用單例模式或者靜態單例模式。
SqlSession:
- 資料庫連線池的一個請求。
- SqlSession的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳作用域是請求或方法作用域。
- 用完之後需要趕緊關閉,否則資源被佔用。
這裡每個Mapper就代表每個具體的業務。
5、解決屬性名和欄位名不一致的問題
新建一個專案,拷貝之前的程式碼,測試實體類欄位不一致的情況。
解決方法:起別名
<select id="getUserById" parameterType="int" resultMap="UserMap">
select id,name,pwd as password,createTime from mybatis.user where id = #{id};
</select>
5.2、resultMap
結果集對映:
<!--結果集對映-->
<resultMap id="UserMap" type="User">
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
<result column="createTime" property="createTime"/>
</resultMap>
<select id="getUserById" parameterType="int" resultMap="UserMap">
select id,name,pwd as password,createTime from mybatis.user where id = #{id};
</select>
resultMap
元素是 MyBatis 中最重要最強大的元素。- ResultMap 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。
- ResultMap最優秀的地方在於,雖然你已經對他相當瞭解了,但是根本就不需要顯式地用到他們。
6、日誌
6.1、日誌工廠
如果一個數據庫操作出現了異常,我們需要拍錯,此時日誌就是最好的助手。
曾經:sout,debug
現在:日誌工廠
- SLF4J
- LOG4J 【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING 【掌握】
- NO_LOGGING
在MyBatis中具體使用哪一個日誌實現,在設定中設定
STDOUT_LOGGING
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
6.2、Log4j
什麼是Log4j?
-
也可以控制每一條日誌的輸出格式
-
定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程
-
可以通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。
- 匯入Log4j的包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
- 編寫配置檔案 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/rzp.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.sq1.PreparedStatement=DEBUG
- 配置log4j為日誌的實現
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
- Log4j的使用
-
在要使用Log4j的類中,匯入包 import org.apache.log4j.Logger;
-
日誌物件,引數為當前類的class物件
Logger logger = Logger.getLogger(UserDaoTest.class);
-
logger.info("info: 測試log4j"); logger.debug("debug: 測試log4j"); logger.error("error:測試log4j");
-
info
-
debug
-
error
-
7、分頁
思考:為什麼分頁?
- 減少資料的處理量
7.1 使用Limit分頁
SELECT * from user limit startIndex,pageSize
使用MyBatis實現分頁,核心SQL
- 介面
List<User> getUserByLimit(Map<String,Integer> map);
- Mapper.xml
<!--分頁查詢-->
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
select * from user limit #{startIndex},#{pageSize};
</select>
- 測試
@Test
public void testGetUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Integer> map = new HashMap<>();
map.put("startIndex",1);
map.put("pageSize",2);
List<User> userList = mapper.getUserByLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
7.2 RowBounds分頁【不建議在開發中使用,沒有sql分頁方便】
不再使用sql實現分頁
- 介面
List<User> getUserByRowBounds();
- mapper.xml
<select id="getUserByRowBounds" resultMap="UserMap">
select * from USER;
</select>
- 測試
@Test
public void testGetUserByRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
RowBounds rowBounds = new RowBounds(0, 3);
List<User> userList = sqlSession.selectList("com.chen.dao.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
7.3、分頁外掛
8、使用註解開發
8.1 面向介面開發
三個面向區別
- 面向物件是指,我們考慮問題時,以物件為單位,考慮它的屬性和方法;
- 面向過程是指,我們考慮問題時,以一個具體的流程(事務過程)為單位,考慮它的實現;
- 介面設計與非介面設計是針對複用技術而言的,與面向物件(過程)不是一個問題,更多的體現就是對系統整體的架構;
8.2 使用註解開發
- 註解在介面上實現
@Select("select * from user")
List<User> getUsers();
- 需要在核心配置檔案中繫結介面【因為已經不存在*mapper.xml檔案了】
<mappers>
<mapper class="com.chen.dao.UserMapper"/>
</mappers>
- 測試
本質:反射機制
底層:動態代理
8.3、註解CRUD
@Select("select * from user")
List<User> getUsers();
@Insert("insert into user (name,pwd,createTime) values(#{name},#{password},#{createTime})")
int addUser(User user);
@Delete("delete from user where id = #{id}")
int delUser(@Param("id") int id);
@Update("update user set name = #{name},pwd = #{password},createTime = #{createTime} where id = #{id}")
int updUser(User user);
關於@Param( )註解
- 基本型別的引數或者String型別,需要加上
- 引用型別不需要加
- 如果只有一個基本型別的話,可以忽略,但是建議大家都加上
- 我們在SQL中引用的就是我們這裡的@Param()中設定的屬性名
#{} ${} 區別
相當於PrepareStatement和Statement的區別
{}可以很大程度防止sql注入
9、Lombok
Lombok專案是一個Java庫,它會自動插入編輯器和構建工具中,Lombok提供了一組有用的註釋,用來消除Java類中的大量樣板程式碼。僅五個字元(@Data)就可以替換數百行程式碼從而產生乾淨,簡潔且易於維護的Java類。
使用步驟:
-
在IDEA中安裝Lombok外掛
-
在專案中匯入lombok的jar包
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency>
-
在實體類上加註解
@Getter and @Setter 新增屬性的get、set方法 @FieldNameConstants @ToString 重寫toString方法 @EqualsAndHashCode 在類上重寫equals和hashCode方法 @AllArgsConstructor 擁有所有屬性的建構函式 @RequiredArgsConstructor @NoArgsConstructor 沒有任何屬性的建構函式 @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog @Data @Builder @SuperBuilder @Singular @Delegate @Value @Accessors @Wither @With @SneakyThrows @val
說明
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
10、多對一處理
多對一的理解:
- 多個學生對應一個老師,即從學生這邊關聯一個老師
10.1、匯入資料
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');
實體類
public class Student {
private int id;
private String name;
private Teacher teacher;
}
public class Teacher {
private int id;
private String name;
}
10.2、按照查詢巢狀處理
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--association關聯屬性 property屬性名 javaType屬性型別 column在多的一方的表中的列名-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--這裡傳遞過來的id,只有一個屬性的時候,下面可以寫任何值
association中column多引數配置:
column="{key=value,key=value}"
其實就是鍵值對的形式,key是傳給下個sql的取值名稱,value是片段一中sql查詢的欄位名。
-->
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{tid}
</select>
10.3、按照結果巢狀處理
<select id="getStudents2" 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 property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
回顧Mysql多對一查詢方式:
- 子查詢(按照查詢巢狀)
- 聯表查詢(按照結果巢狀)
11、一對多處理
一對多的理解:
- 一個老師擁有多個學生
- 如果對於老師這邊,就是一個一對多的現象,即從一個老師下面擁有一群學生(集合)
實體類
public class Student {
private int id;
private String name;
private int tid;
}
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
按照結果巢狀處理
<select id="getTeacher" resultMap="TeacherStudents">
select t.id tid,t.name tname,s.id sid,s.name sname
from teacher t,student s
where t.id = s.tid and t.id = #{tid}
</select>
<resultMap id="TeacherStudents" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
按查詢巢狀處理
<select id="getTeacher2" resultMap="TeacherStudents2">
select * from teacher where id = #{tid}
</select>
<resultMap id="TeacherStudents2" type="Teacher">
<!--column是一對多的外來鍵 , 寫的是一的主鍵的列名-->
<collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid = #{id}
</select>
小結
-
關聯 - association【多對一】
-
集合 - collection【一對多】
-
javaType和ofType
- javaType用來指定實體類中的型別
- ofType用來指定對映到List或者集合中的pojo型別,泛型中的約束型別
注意點
- 保證SQL的可讀性,儘量保證通俗易懂
- 注意一對多和多對一,屬性名和欄位的問題
- 如果問題不好排查錯誤,可以使用日誌,建議使用Log4j
12、動態SQL
什麼是動態SQL:動態SQL就是根據不同的條件生成不同的SQL語句
所謂的動態SQL,本質上還是SQL語句,只是我們可以在SQL層面,去執行一個邏輯程式碼
動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記新增必要的空格,還要注意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。
12.1、搭建環境
CREATE TABLE `mybatis`.`blog` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT '部落格id',
`title` varchar(30) NOT NULL COMMENT '部落格標題',
`author` varchar(30) NOT NULL COMMENT '部落格作者',
`create_time` datetime(0) NOT NULL COMMENT '建立時間',
`views` int(30) NOT NULL COMMENT '瀏覽量',
PRIMARY KEY (`id`)
)
- 建立Mybatis基礎工程
- IDutil工具類
public class IDUtil {
public static String getID(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
- 實體類編寫
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Date;
@Data
@AllArgsConstructor
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
- 編寫Mapper介面及xml檔案
import java.util.List;
import java.util.Map;
public interface BlogMapper {
int addBlog(Blog blog);
}
<?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">
<mapper namespace="com.chen.dao.BlogMapper">
<insert id="addBlog" parameterType="Blog">
insert into mybatis.blog (id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
</mapper>
5、mybatis核心配置檔案,下劃線駝峰自動轉換
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置檔案-->
<configuration>
<properties resource="db.properties"/>
<settings>
<!--標準的日誌-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<package name="com.chen.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.chen.dao"/>
</mappers>
</configuration>
12.2、if語句
需求:根據作者名字和部落格名字來查詢部落格!如果作者名字為空,那麼只根據部落格名字查詢,反之,則根據作者名來查詢
1、編寫介面類
List<Blog> queryBlogIf(Map map);
2、編寫Mapper.xml
<!--需求1:
根據作者名字和部落格名字來查詢部落格!
如果作者名字為空,那麼只根據部落格名字查詢,反之,則根據作者名來查詢
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="Blog">
select * from blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
3、測試
@Test
public void testQueryBlogIf(){
SqlSession sqlSession = MybatisUtils.getSqlSession(true);
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("title","第一篇部落格");
map.put("author","chen1");
List<Blog> blogs = mapper.queryBlogIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
12.3、where語句
修改上面的SQL語句
<select id="queryBlogIf" 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>
這個“where”標籤會知道如果它包含的標籤中有返回值的話,它就插入一個‘where’。此外,如果標籤返回的內容是以AND 或OR 開頭的,則它會剔除掉。
12.4、set語句
同理,上面的對於查詢 SQL 語句包含 where 關鍵字,如果在進行更新操作的時候,含有 set 關鍵詞,我們怎麼處理呢?
1、編寫介面方法
int updateBlog(Map map);
2、sql配置檔案
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id}
</update>
3、測試
@Test
public void testUpdateBlog(){
SqlSession sqlSession = MybatisUtils.getSqlSession(true);
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map = new HashMap<>();
map.put("title","修改後的標題");
map.put("author","小陳");
map.put("id","1af1a344e8a4464ab662697c1cc492c5");
mapper.updateBlog(map);
sqlSession.close();
}
12.5、choose語句
有時候,我們不想用到所有的查詢條件,只想選擇其中的一個,查詢條件有一個滿足即可,使用 choose 標籤可以解決此類問題,類似於 Java 的 switch 語句
1、編寫介面方法
List<Blog> queryBlogChoose(Map map);
2、sql配置檔案
<select id="queryBlogChoose" 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>
3、測試
@Test
public void testQueryBlogChoose(){
SqlSession sqlSession = MybatisUtils.getSqlSession(true);
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map = new HashMap<>();
//map.put("title","修改後的標題");
//map.put("author","小陳");
map.put("views",111);
List<Blog> blogs = mapper.queryBlogChoose(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
12.6、SQL片段
有的時候,可以將一些通用的sql片段抽取出來方便複用
1、使用SQL標籤抽取通用的部分
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
2、在需要使用的地方使用include標籤引用
<select id="queryBlogIf" parameterType="map" resultType="Blog">
select * from blog
<where>
<include refid="if-title-author"/>
</where>
</select>
注意事項:
- 最好基於單表定義SQL片段
- 不要存在where標籤
12.7、foreach標籤
1、編寫介面
List<Blog> queryBlogForeach(Map map);
2、編寫SQL語句
<select id="queryBlogForeach" parameterType="map" resultType="Blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="(" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
3、測試
@Test
public void testQueryBlogForeach(){
SqlSession sqlSession = MybatisUtils.getSqlSession(true);
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map = new HashMap<>();
List<String> ids = new ArrayList<>(Arrays.asList("1","2","3"));
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
小結:其實動態 sql 語句的編寫往往就是一個拼接的問題,為了保證拼接準確,我們最好首先要寫原生的 sql 語句出來,然後在通過 mybatis 動態sql 對照著改,防止出錯。多在實踐中使用才是熟練掌握它的技巧。
動態SQL在開發中大量使用,一定要熟練掌握!
13、快取
13.1、簡介
1、什麼是快取 [ Cache ]?
- 存在記憶體中的臨時資料。
- 將使用者經常查詢的資料放在快取(記憶體)中,使用者去查詢資料就不用從磁碟上(關係型資料庫資料檔案)查詢,從快取中查詢,從而提高查詢效率,解決了高併發系統的效能問題。
2、為什麼使用快取?
- 減少和資料庫的互動次數,減少系統開銷,提高系統效率。
3、什麼樣的資料能使用快取?
- 經常查詢並且不經常改變的資料。
13.2、Mybatis快取
- MyBatis包含一個非常強大的查詢快取特性,它可以非常方便地定製和配置快取。快取可以極大的提升查詢效率。
- MyBatis系統中預設定義了兩級快取:一級快取和二級快取
- 預設情況下,只有一級快取開啟。(SqlSession級別的快取,也稱為本地快取)
- 二級快取需要手動開啟和配置,他是基於namespace級別的快取。
- 為了提高擴充套件性,MyBatis定義了快取介面Cache。我們可以通過實現Cache介面來自定義二級快取
13.3、一級快取
一級快取也叫本地快取:
- 與資料庫同一次會話期間查詢到的資料會放在本地快取中。
- 以後如果需要獲取相同的資料,直接從快取中拿,沒必須再去查詢資料庫;
測試步驟:
- 開啟日誌
- 測試在一個SqlSession中查詢兩次相同記錄
- 檢視日誌輸出
快取失效的情況:
-
查詢不同的東西
-
增刪改操作,可能會改變原來的資料,所以必定會重新整理快取
-
查詢不同的Mapper.xml
-
手動清理快取
sqlSession.clearCache();
小結:
一級快取預設是開啟的,只在一次SqlSession中有效,也就是拿到連線到關閉連線這個區間段。
底層就是一個Map
13.4、二級快取
- 二級快取也叫全域性快取,一級快取作用域太低了,所以誕生了二級快取
- 基於namespace級別的快取,一個名稱空間,對應一個二級快取;
- 工作機制
- 一個會話查詢一條資料,這個資料就會被放在當前會話的一級快取中;
- 如果當前會話關閉了,這個會話對應的一級快取就沒了;但是我們想要的是,會話關閉了,一級快取中的資料被儲存到二級快取中;
- 新的會話查詢資訊,就可以從二級快取中獲取內容;
- 不同的mapper查出的資料會放在自己對應的快取(map)中;
使用快取
1、開啟全域性快取【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
2、去每個mapper.xml中配置使用二級快取,這個配置非常簡單;【xxxMapper.xml】
<cache/>
官方示例=====>檢視官方文件
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個更高階的配置建立了一個 FIFO 快取,每隔 60 秒重新整理,最多可以儲存結果物件或列表的 512 個引用,而且返回的物件被認為是隻讀的,因此對它們進行修改可能會在不同執行緒中的呼叫者產生衝突。
3、測試
問題:我們需要將實體類序列化,否則就會報錯
Caused by: java.io.NotSerializableException:com.chen.pojo.User
小結:
- 只要開啟了二級快取,在同一個Mapper下就有效
- 所有資料都會先放在一級快取中
- 只有當會話提交,或者關閉的時候,才會提交到二級快取中
13.5、快取原理
只有查詢才有快取,根據資料是否需要快取(修改是否頻繁選擇是否開啟) useCache="false"
<select id="getUserByID" resultType="User" useCache="false">
select * from user where id = #{id}
</select>
13.6、自定義快取-ehcache
Ehcache是一種廣泛使用的開源Java分散式快取。主要面向通用快取
- 導包
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
- 在mapper中指定使用我們的ehcache快取實現
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
- 編寫ehcache.xml檔案,如果在載入時未找到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:為快取路徑,ehcache分為記憶體和磁碟兩級,此屬性定義磁碟的快取位置。引數解釋如下:
user.home – 使用者主目錄
user.dir – 使用者當前工作目錄
java.io.tmpdir – 預設臨時檔案路徑
-->
<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"/>
<!--
defaultCache:預設快取策略,當ehcache找不到定義的快取時,則使用這個快取策略。只能定義一個。
-->
<!--
name:快取名稱。
maxElementsInMemory:快取最大數目
maxElementsOnDisk:硬碟最大快取個數。
eternal:物件是否永久有效,一但設定了,timeout將不起作用。
overflowToDisk:是否儲存到磁碟,當系統當機時
timeToIdleSeconds:設定物件在失效前的允許閒置時間(單位:秒)。僅當eternal=false物件不是永久有效時使用,可選屬性,預設值是0,也就是可閒置時間無窮大。
timeToLiveSeconds:設定物件在失效前允許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false物件不是永久有效時使用,預設是0.,也就是物件存活時間無窮大。
diskPersistent:是否快取虛擬機器重啟期資料 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:這個引數設定DiskStore(磁碟快取)的快取區大小。預設是30MB。每個Cache都應該有自己的一個緩衝區。
diskExpiryThreadIntervalSeconds:磁碟失效執行緒執行時間間隔,預設是120秒。
memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理記憶體。預設策略是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。
clearOnFlush:記憶體數量最大時是否清除。
memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,預設策略)、FIFO(先進先出)、LFU(最少訪問次數)。
FIFO,first in first out,這個是大家最熟的,先進先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,快取的元素有一個hit屬性,hit值最小的將會被清出快取。
LRU,Least Recently Used,最近最少使用的,快取的元素有一個時間戳,當快取容量滿了,而又需要騰出地方來快取新的元素的時候,那麼現有快取元素中時間戳離當前時間最遠的元素將被清出快取。
-->
</ehcache>