Mybatis官方文檔:
官網文檔: https://mybatis.org/mybatis-3/zh/getting-started.html
MyBatis
1、簡介
1.1 什么是Mybatis

- MyBatis 是一款優秀的持久層框架;
- 它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)為數據庫中的記錄。
1.2 持久化
數據持久化
- 持久化就是將程序的數據在持久狀態和瞬時狀態轉化的過程
- 內存:斷電即失
- 數據庫(Jdbc),io文件持久化。
為什么要持久化?
有一些對象,不能讓他丟掉
內存太貴
1.3 持久層
Dao層、Service層、Controller層
- 完成持久化工作的代碼塊
- 層界限十分明顯
1.4 為什么需要MyBatis
-
幫助程序員將數據存入到數據庫中
-
方便
-
傳統的JDBC代碼太復雜了,簡化,框架,自動化
-
不用MyBatis也可以,技術沒有高低之分
-
優點:
- 簡單易學
- 靈活
- sql和代碼的分離,提高了可維護性。
- 提供映射標簽,支持對象與數據庫的orm字段關系映射
- 提供對象關系映射標簽,支持對象關系組建維護
- 提供xml標簽,支持編寫動態sql
2、第一個Mybatis程序
思路:搭建環境 --> 導入MyBatis --> 編寫代碼 --> 測試
2.1 搭建環境
創建數據庫
CREATE DATABASE `mybatis`; USE `mybatis`; CREATE TABLE `user`( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID', `name` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '用戶名稱', `pwd` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '用戶密碼', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; INSERT INTO `user`(`id`,`name`,`pwd`) VALUES (1,"狂神","123456"), (2,"通達","123456"), (3,"藍凌","123456");
新建項目
1.創建一個普通的maven項目
2.刪除src目錄 (就可以把此工程當做父工程了,然后創建子工程)
3.導入maven依賴
<!--導入依賴-->
<dependencies>
<!--mysqlq驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
4.創建一個Module
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?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> </configuration>
詳解:<?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默認=id-->
<environments default="development">
<!--environment環境 id-->
<environment id="development">
<!--事務管理 JDBC-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--<property name="driver" value="${driver}"/>-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<!--<property name="url" value="${url}"/>-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&charsetEncoding=UTF-8"/>
<!--<property name="username" value="${username}"/>-->
<property name="username" value="root"/>
<!--<property name="password" value="${password}"/>-->
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--<mappers>-->
<!-- <mapper resource="com/tongda/dao/UserMapper.xml"/>-->
<!--</mappers>-->
</configuration>
此處設useSSL=false。
- 編寫mybatis工具類
//SqlSessionFactory -->SqlSession public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { //使用Mybaties第一步:獲取sqlSessionFactory對象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (Exception e) { e.printStackTrace(); } } //既然有了 SqlSessionFactory,顧名思義,我們可以從中獲得 SqlSession 的實例。 // SqlSession 提供了在數據庫執行 SQL 命令所需的所有方法。你可以通過 SqlSession 實例來直接執行已映射的 SQL 語句。 public static SqlSession getSqlSession(){ // SqlSession sqlSession = sqlSessionFactory.openSession(); // return sqlSession; return sqlSessionFactory.openSession(); } }
2.3 編寫代碼
- 實體類
//實體類 public class User { private int id; private String name; private String pwd; public User() { } public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } 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 + '\'' + '}'; } }
- Dao接口
public interface UserDao { List<User> getUserList(); }
- 接口實現類由原來的UserDaoImpl轉變成一個Mapper配置文件。
<?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接口-->
<!--命名空間-->
<!--重點namespace\id\resultType都要對應,不能亂寫-->
<mapper namespace="com.tongda.dao.UserDao">
<!--sql:select查詢語句-->
<!--返回結果集:resulttype一個,resultmap多個-->
<!--返回結果集User是UserDao泛型<User>-->
<select id="getUserList" resultType="com.kuang.pojo.User">
select * from mybatis.user
</select>
</mapper>
junit測試.
注意點:會報錯
org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.
MapperRegistry是什么?
核心配置文件中注冊mappers
@Test public void test(){ //1.獲取SqlSession對象 SqlSession sqlSession = MybatisUtils.getSqlSession(); //2.執行SQL // 方式一:getMapper UserDao userDao = sqlSession.getMapper(UserDao.class); List<User> userList = userDao.getUserList(); for (User user : userList) { System.out.println(user); } //關閉sqlSession sqlSession.close(); }
mybatis-config.xml中
<!--每一個Mapper.xml都需要在Mybatis核心配置文件中注冊-->
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
可能會遇到的問題:
- 配置文件沒有注冊
- 綁定接口錯誤
- 方法名不對
- 返回類型不對
- Maven導出資源問題
3、CURD
1. namespace
namespace中的包名要和Dao/Mapper接口的包名一致
<?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接口--> <!--命名空間--> <!--重點namespace\id\resultType都要對應,不能亂寫--> <mapper namespace="com.tongda.dao.UserMapper"> <!--sql:select查詢語句--> <!--返回結果集:resulttype一個,resultmap多個--> <!--返回結果集User是UserDao泛型<User>--> <select id="getUserList" resultType="com.tongda.pojo.User"> select * from mybatis.user </select> </mapper>
2. select
選擇,查詢語句;
id:就是對應的namespace中的方法名;
resultType : Sql語句執行的返回值;
parameterType : 參數類型;
1.編寫接口:UserMapper.java
public interface UserMapper { //查詢所有用戶 public List<User> getUserList(); //插入用戶 public void addUser(User user); }
2.編寫對應的mapper中的sql語句:UserMapper.xml
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,password) values (#{id}, #{name}, #{password})
</insert>
3.測試:UserMapperTest.java
@Test public void test2() { SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = new User(3,"黑子","666"); mapper.addUser(user); //增刪改一定要提交事務 sqlSession.commit(); //關閉sqlSession sqlSession.close(); }
注意:增刪改查一定要提交事務:
sqlSession.commit();
增刪改查全部流程實現
3. Insert
4. update
5. Delete
UserMapper.java
//Dao以后等價mapper public interface UserMapper { //返回所有用戶 List<User> getUserList(); //根據Id查詢用戶,User為返回值用戶 User getUserById(int id); //insert一個用戶,參數是對象User int addUser(User user); //修改用戶 int updateUser(User user); //刪除一個用戶 int deleteUser(int id); }
UserMapper.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"> <!--namespace=綁定一個對應的Dao/Mapper接口--> <!--命名空間--> <!--重點namespace\id\resultType都要對應,不能亂寫--> <mapper namespace="com.tongda.dao.UserMapper"> <!--sql:select查詢語句--> <!--返回結果集:resulttype一個,resultmap多個--> <!--返回結果集User是UserDao泛型<User>--> <select id="getUserList" resultType="com.tongda.pojo.User"> select * from mybatis.user </select> <!--對應id查詢用戶,返回一個resultType結果集類型,參數類型int--> <select id="getUserById" resultType="com.tongda.pojo.User" parameterType="int"> select * from user where id = #{id}; </select> <!--insert一個用戶--> <insert id="addUser" parameterType="com.tongda.pojo.User"> insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd}); </insert> <!--updata修改用戶,根據id--> <update id="updateUser" parameterType="com.tongda.pojo.User"> update mybatis.user set name=#{name},pwd=#{pwd} where id =#{id}; </update> <delete id="deleteUser" parameterType="int"> delete from mybatis.user where id =#{id}; </delete> </mapper>
UserDaoTest.java
package com.tongda.dao; import com.tongda.pojo.User; import com.tongda.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; /** * @Author JunLong * @Date 2022/4/5 6:59 * @Version 1.0 */ /*注意點: org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry. MapperRegistry是什么? 核心配置文件中注冊mappers */ public class UserDaoTest { @Test public void test(){ //第一步:獲取SqlSession對象,工具類MybatisUtils中 SqlSession sqlSession = MybatisUtils.getSqlSession(); //執行Sql:方式一getMapper UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUserList(); // 方法一: for (User user : userList) { System.out.println(user); } //不推薦:方法二 // List<User> userList = sqlSession.selectList("com.tongda.UserDao.getUserList"); // for (Object user : userList) { // System.out.println(user); // // } //關閉sqlSession sqlSession.close(); } @Test public void getUserById(){ //第一步固定步驟寫法 SqlSession sqlSession = MybatisUtils.getSqlSession(); //獲得UserMapper接口 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //getUserbyid(用戶id號) User user1 = mapper.getUserById(1); User user2 = mapper.getUserById(2); User user3 = mapper.getUserById(2); System.out.println(user1); System.out.println(user2); System.out.println(user3); sqlSession.close(); } //增刪改必須提交事務 @Test public void addUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int res = mapper.addUser(new User(4, "哼哈", "123456")); if (res>0){ System.out.println("插入成功!"); } // 提交事務,增刪改必須提交事務 sqlSession.commit(); //關閉 sqlSession.close();; } @Test public void updataUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.updateUser(new User(4,"呵呵","123123")); sqlSession.commit(); sqlSession.close(); } @Test public void deleteUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.deleteUser(4); sqlSession.commit(); sqlSession.close(); } }
6.分析錯誤
1.標簽不要匹配錯 com.tongda.dao.UserMapper;
2.resource綁定mapper,需要使用路徑! com/tongda/dao/UserMapper 注意分隔符;
3.程序配置文件必須符合規范;
4.NullPointerException,沒有注冊到資源Utils中sqlSessionFactory作用域問題;
5.亂碼問題<property name="url" value="jdbc:mysql://localhost:3306/自己的庫?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>,或者idea-setting-file encoding-utf-8
6.maven資源沒有導出問題,pom.xml中在build中配置resources,來防止我們資源導出失敗的問題
7.萬能的Map
假設,我們的實體類,或者數據庫中的表,字段或者參數過多,我們應該考慮使用Map!
UserMapper接口
//用萬能Map插入用戶
public void addUser2(Map<String,Object> map);
UserMapper.xml
<!--對象中的屬性可以直接取出來 傳遞map的key-->
<insert id="addUser2" parameterType="map">
insert into user (id,name,password) values (#{userid},#{username},#{userpassword})
</insert>
測試UserDaoTest
@Test public void test3(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Object> map = new HashMap<String, Object>(); map.put("userid",4); map.put("username","王虎"); map.put("userpassword",789); mapper.addUser2(map); //提交事務 sqlSession.commit(); //關閉資源 sqlSession.close(); }
Map傳遞參數,直接在sql中取出key即可! 【parameter=“map”】
對象傳遞參數,直接在sql中取出對象的屬性即可! 【parameter=“Object”】
只有一個基本類型參數的情況下,可以直接在sql中取到
多個參數用Map , 或者注解!
7.模糊查詢
模糊查詢怎么寫?
Java代碼執行的時候,傳遞通配符% %
<select id="getUserLike1" parameterType="String" resultType="com.bupt.pojo.User"> select * from user where username like #{name} </select>
經常碰到這樣的面試題目:#{}和${}的區別是什么?
網上的答案是:#{}是預編譯處理,$ {}是字符串替換。
mybatis在處理#{}時,會將sql中的#{}替換為?號,調用PreparedStatement的set方法來賦值;
mybatis在處理 <span id="MathJax-Span-2" class="mrow"><span id="MathJax-Span-3" class="texatom"><span id="MathJax-Span-4" class="mrow"><span id="MathJax-Span-5" class="texatom"><span id="MathJax-Span-6" class="mrow"><span id="MathJax-Span-7" class="mo">時<span id="MathJax-Span-8" class="texatom"><span id="MathJax-Span-9" class="mrow"><span id="MathJax-Span-10" class="mo">,<span id="MathJax-Span-11" class="texatom"><span id="MathJax-Span-12" class="mrow"><span id="MathJax-Span-13" class="mo">就<span id="MathJax-Span-14" class="texatom"><span id="MathJax-Span-15" class="mrow"><span id="MathJax-Span-16" class="mo">是<span id="MathJax-Span-17" class="texatom"><span id="MathJax-Span-18" class="mrow"><span id="MathJax-Span-19" class="mo">把時,就是把{ } 替換成變量的值。
使用 #{} 可以有效的防止SQL注入,提高系統安全性。
sql注入指的是用戶輸入 or 1=1等變量導致原sql語句邏輯發生了變化,使得可以直接進入數據庫,而使用預編譯的方法可以讓sql語句只把傳入的數據當做參數進行處理
而不會改變原有sql的邏輯,preparestatement和#{}都是把變量預編譯,防止sql注入。
4、配置解析
1. 核心配置文件
-
mybatis-config.xml
-
Mybatis的配置文件包含了會深深影響MyBatis行為的設置和屬性信息。
configuration(配置)
properties(屬性) 必須掌握
settings(設置) 必須掌握
typeAliases(類型別名) 必須掌握
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環境配置) 必須掌握
environment(環境變量)
transactionManager(事務管理器)
dataSource(數據源)
databaseIdProvider(數據庫廠商標識)
mappers(映射器)
2. 環境配置 environments
MyBatis 可以配置成適應多種環境
不過要記住:盡管可以配置多個環境,但每個 SqlSessionFactory 實例只能選擇一種環境

學會使用配置多套運行環境!
注意一些關鍵點:
- 默認使用的環境 ID(比如:default="development")。
- 每個 environment 元素定義的環境 ID(比如:id="development")。
- 事務管理器的配置(比如:type="JDBC")。
- 數據源的配置(比如:type="POOLED")。
默認環境和環境 ID 顧名思義。 環境可以隨意命名,但務必保證默認的環境 ID 要匹配其中一個環境 ID。
MyBatis默認的事務管理器就是JDBC ,連接池:POOLED
3. 屬性 properties
我們可以通過properties屬性來實現引用配置文件
這些屬性可以在外部進行配置,並可以進行動態替換。你既可以在典型的 Java 屬性文件中配置這些屬性,也可以在 properties 元素的子元素中設置。【db.poperties】
1.編寫一個配置文件
db.properties
driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC username=root password=123456
2.在核心配置文件中引入
<!--引用外部配置文件-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<?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=“路徑”--> <properties resource="db.properties"/> <!--environments多環境配置 default默認=id--> <environments default="development"> <!--environment環境 id--> <environment id="development"> <!--事務管理 JDBC--> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!--引入外部配置文件后,不用寫死value值--> <!--<property name="driver" value="com.mysql.cj.jdbc.Driver"/>--> <property name="driver" value="${driver}"/> <!--<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>--> <property name="url" value="${url}"/> <!--<property name="username" value="root"/>--> <property name="username" value="${username}"/> <!--<property name="password" value="123456"/>--> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!--每一個Mapper.xml都需要在Mybatis核心配置文件中注冊--> <mappers> <mapper resource="com/tongda/dao/UserMapper.xml" /> </mappers> </configuration>
1.可以直接引入外部文件
2.可以在其中增加一些屬性配置
3.如果兩個文件有同一個字段,優先使用外部配置文件的
注意點:核心配置文件書寫標簽配置順序
properties放在最前面

4. 類型別名 typeAliases
-
類型別名可為 Java 類型設置一個縮寫名字。 它僅用於 XML 配置.
-
意在降低冗余的全限定類名書寫。
mybatis-config.xml中
<!--注意標簽書寫順序,properties,setting,typeAlias-->
<!--可以給實體類別名-->
<typeAliases>
<!--指定一個類,起別名,可以任意別名-->
<typeAlias type="com.tongda.pojo.User" alias="User"/>
</typeAliases>

UserMapper.xml
<!--resultType=“com.tongda.pojo.User",前面mybatis-config.xml中已別名-->
<select id="getUserList" resultType="User">
select * from mybatis.user </select>
也可以指定一個包,每一個在包 domain.blog 中的 Java Bean,在沒有注解的情況下,會使用 Bean 的首字母小寫的非限定類名來作為它的別名。
比如 com.tongda.pojo.User 的別名為 user,;若有注解,則別名為其注解值。見下面的例子:
<typeAliases>
<!--指定一個包,起別名,指定別名為user-->
<package name="com.tongda.pojo"/>
</typeAliases>

在實體類比較少的時候,使用第一種方式。
如果實體類十分多,建議用第二種掃描包的方式。
第一種可以DIY別名,第二種不行,如果非要改,需要在實體上增加注解。
@Alias("user")
public class User {
...
}
5. 設置 Settings
這是 MyBatis 中極為重要的調整設置,它們會改變 MyBatis 的運行時行為。



6. 其他配置
- typeHandlers(類型處理器)
- objectFactory(對象工廠)
- plugins 插件
- mybatis-generator-core
- mybatis-plus
- 通用mapper
7. 映射器 mappers
MapperRegistry:注冊綁定我們的Mapper文件;
方式一:【推薦使用】
<!--每一個Mapper.xml都需要在MyBatis核心配置文件中注冊-->
<mappers>
<mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>
方式二:使用class文件綁定注冊
<!--每一個Mapper.xml都需要在MyBatis核心配置文件中注冊-->
<mappers>
<mapper class="com.kuang.dao.UserMapper"/>
</mappers>
注意點:
- 接口和他的Mapper配置文件必須同名,如UserMapper.java和UserMapper.xml
- 接口和他的Mapper配置文件必須在同一個包下
方式三:使用包掃描進行注入
<mappers>
<package name="com.kuang.dao"/>
</mappers>
8. 作用域和生命周期

聲明周期和作用域是至關重要的,因為錯誤的使用會導致非常嚴重的並發問題。
SqlSessionFactoryBuilder:
一旦創建了SqlSessionFactory,就不再需要它了
局部變量
SqlSessionFactory: *****最核心*****
說白了就可以想象為:數據庫連接池
SqlSessionFactory一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建一個實例。
因此SqlSessionFactory的最佳作用域是應用作用域(ApplocationContext)。
最簡單的就是使用單例模式或靜態單例模式。
SqlSession:
連接到連接池的一個請求
SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。
用完之后需要趕緊關閉,否則資源被占用!

這里每一個mapper,都是代表一個業務模塊
5、解決屬性名和字段名不一致的問題
1. 問題
數據庫中的字段

新建一個項目,拷貝之前的,測試實體類字段不一致的情況

測試出現問題
// select * from user where id = #{id} // 類型處理器 // select id,name,pwd from user where id = #{id}
解決方法:
- 起別名
<select id="getUserById" resultType="com.kuang.pojo.User"> select id,name,pwd as password from USER where id = #{id} </select>
2. resultMap
結果集映射
id name pwd
id name password
<!--結果集映射,id="UserMap"對應resultMap="UserMap"-->
<resultMap id="UserMap" type="User">
<!--column數據庫中的字段,property實體類中的屬性-->
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<result column="pwd" property="password"></result>
</resultMap>
<select id="getUserList" resultMap="UserMap">
select * from USER
</select>

resultMap 元素是 MyBatis 中最重要最強大的元素。
ResultMap 的設計思想是,對簡單的語句做到零配置,對於復雜一點的語句,只需要描述語句之間的關系就行了。
ResultMap 的優秀之處——你完全可以不用顯式地配置它們。
<!--結果集映射,id="UserMap"對應resultMap="UserMap"-->
<resultMap id="UserMap" type="User">
<!--column數據庫中的字段,property實體類中的屬性-->
<--相同字段和屬性不用寫-->
<!--<result column="id" property="id"></result>-->
<!--<result column="name" property="name"></result>-->
<!--只寫字段、屬性不同的-->
<result column="pwd" property="password"></result>
</resultMap>
<select id="getUserList" resultMap="UserMap">
select * from USER
</select>
如果這個世界總是這么簡單就好了。
6、日志
6.1 日志工廠
如果一個數據庫操作,出現了異常,我們需要排錯,日志就是最好的助手!
曾經:sout、debug
現在:日志工廠

- SLF4J
- LOG4J 【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING 【掌握】 標准日志輸出
- NO_LOGGING
在MyBatis中具體使用哪一個日志實現,在設置中設定
STDOUT_LOGGING 標准日志輸出(無需配置)
mybatis-config.xml中位置在properties后、TypeAliases前
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

6.2 Log4j
什么是Log4j?
Log4j是Apache的一個開源項目,通過使用Log4j,我們可以控制日志信息輸送的目的地是控制台、文件、GUI組件;
我們也可以控制每一條日志的輸出格式;
通過定義每一條日志信息的級別,我們能夠更加細致地控制日志的生成過程;
最令人感興趣的就是,這些可以通過一個配置文件來靈活地進行配置,而不需要修改應用的代碼。
先導入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/hzh.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實現
mybatis-config.xml中位置在properties后、TypeAliases前
<!--日志配置-->
<settings>
<setting name="logImpl" value=""/>
</settings>
測試運行
Log4j簡單使用
-
在要使用Log4j的類中,導入包 import org.apache.log4j.Logger;
-
日志對象,參數為當前類的class對象
Logger logger = Logger.getLogger(UserDaoTest.class);
3.日志級別
logger.info("info: 測試log4j");
logger.debug("debug: 測試log4j");
logger.error("error:測試log4j");
7、分頁
思考:為什么分頁?
- 減少數據的處理量
7.1 使用Limit分頁(通過sql層面實現)
SELECT * from user limit startIndex,pageSize
select * from user limit 3; #[0,n]
使用MyBatis實現分頁,核心SQL
1.接口UserMapper.java
//分頁推薦方法
List<User> getUserByLimit(Map<String,Integer> map);
2.Mapper.xml
<!--分頁-->
<!--死記startIndex,pageSize參數下標-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
3.測試
//分頁 @Test public void getUserByLimit(){ //獲取Session SqlSession sqlSession = MybatisUtils.getSqlSession(); //反射獲得Usermapper接口類 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //使用接口類中方法,參數是map,需要構造 HashMap<String, Integer> map = new HashMap<>(); //取值要對應Usermapper.xml中的參數 //設置從0開始 map.put("startIndex",1); //設置到2結束 map.put("pageSize",2); //返回結果集 List<User> userList = mapper.getUserByLimit(map); //循環結果 for (User user : userList) { System.out.println(user); }
sqlSession.close(); }
7.2 RowBounds分頁
不再使用SQL實現分頁
1.接口
//分頁2 List<User> getUserByRowBounds();
2.mapper.xml
<!--分頁2,了解下-->
<select id="getUserByRowBounds" resultMap="UserMap">
select * from mybatis.user
</select>
<!--或者,分頁查詢2-->
<select id="getUserByRowBounds">
select * from user limit #{startIndex},#{pageSize}
</select>
3.測試
public void getUserByRowBounds(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //RowBounds實現 RowBounds rowBounds = new RowBounds(1, 2); //通過Java代碼層面實現分頁 List<User> userList = sqlSession.selectList("com.kaung.dao.UserMapper.getUserByRowBounds", null, rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
7.3 分頁插件(通過第三方插件實現)
mybatis分頁插件
注:不管使用哪種分頁它的底層都是limi
8、使用注解開發
8.1 面向接口開發
根本原因主要為了:==解耦==
三個面向區別
- 面向對象是指,我們考慮問題時,以對象為單位,考慮它的屬性和方法;
- 面向過程是指,我們考慮問題時,以一個具體的流程(事務過程)為單位,考慮它的實現;
- 接口設計與非接口設計是針對復用技術而言的,與面向對象(過程)不是一個問題,更多的體現就是對系統整體的架構;
8.2 使用注解開發
1.注解在接口上實現
@Select("select * from user")
List<User> getUsers();
2.需要在核心配置文件中綁定接口
<mappers>
<mapper class="com.kuang.dao.UserMapper"/>
</mappers>
3.測試
對於增刪改需要把opensession的參數設置為true,這樣就等價於commit操作了,否則無法commit,及時寫了commit,
mybatisUtils.java工具類中
//既然有了 SqlSessionFactory,顧名思義,我們可以從中獲得 SqlSession 的實例。 // SqlSession 提供了在數據庫執行 SQL 命令所需的所有方法。你可以通過 SqlSession 實例來直接執行已映射的 SQL 語句。 public static SqlSession getSqlSession() { //獲取要提升作用域 //固定代碼 // 從 SqlSessionFactory 中獲取 SqlSession // SqlSession sqlSession = sqlSessionFactory.openSession(); // return sqlSession; //優化 return sqlSessionFactory.openSession(); } // 默認參數不提交事務 public static SqlSession getSqlSession() { return getSqlSession(false); } //如果添加這個參數,就會自動提交事務 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); }
UserMapper.java接口
public interface UserMapper { //不在使用mapper.xml,使用注解 @Select("select * from user") List<User> getUsers(); //去mybatis-config.xml中綁定接口 //方法存在多個參數,所有的參數前面必須加上@param("id"),規范 @Select("select * from user where id = ${id}") //注意取參對應@param("參數名qid“),id=#{參數名qid} // User getUserByID(@Param("qid") int id); User getUserByID(@Param("id") int id); @Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})") // insert into user (id, name, pwd) VALUES (4,"李四","123456"); 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=#{uid}") int deleteUser(@Param("uid") int id); }
UserDaoTest.java
對於增刪改需要把opensession的參數設置為true,這樣就等價於commit操作了,否則無法commit,及時寫了commit,
public class UserDaoTest { @Test public void test(){ 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(); } @Test public void getUserById(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User userByID = mapper.getUserByID(1); System.out.println(userByID); sqlSession.close(); } @Test public void addUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.addUser(new User(4,"李四","123456")); // sqlSession.commit(); sqlSession.close(); } @Test public void UpdateUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.updateUser(new User(4,"馬六","123123")); // sqlSession.commit(); sqlSession.close(); } @Test public void deleteUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.deleteUser(4); // sqlSession.commit(); sqlSession.close(); }
本質:反射機制實現
底層:動態代理
MyBatis詳細執行流程
8.3 注解CURD
Utils工具類
//如果添加這個參數,就會自動提交事務 public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(true); }
UserMapper.java接口類
public interface UserMapper { //不在使用mapper.xml,使用注解 @Select("select * from user") List<User> getUsers(); //去mybatis-config.xml中綁定接口 //方法存在多個參數,所有的參數前面必須加上@param("id"),規范 @Select("select * from user where id = #{id}") //注意取參對應@param("參數名“),id=#{參數名} // User getUserByID(@Param("qid") int id); User getUserByID(@Param("id") int id); @Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})") // insert into user (id, name, pwd) VALUES (4,"李四","123456"); int addUser(User user); @Update("update user set name=#{name},pwd=#{password} where id =#{id}") int updateUser(User user); //方法存在多個參數,所有的參數前面必須加上@Param("id")注解 @Delete("delete from user where id=#{uid},name=#{uname}") int deleteUser(@Param("uid") int id,@Param("uname") String name); }
UserDaoTest.java測試類
對於增刪改需要把opensession的參數設置為true,這樣就等價於commit操作了,否則無法commit,及時寫了commit,
public class UserDaoTest { @Test public void test(){ 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(); } @Test public void getUserById(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User userByID = mapper.getUserByID(1); System.out.println(userByID); sqlSession.close(); } @Test public void addUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.addUser(new User(4,"李四","123456")); // sqlSession.commit(); sqlSession.close(); } @Test public void UpdateUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.updateUser(new User(4,"馬六","123123")); // sqlSession.commit(); sqlSession.close(); } @Test public void deleteUser(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); mapper.deleteUser(4,"李四"); // sqlSession.commit(); sqlSession.close(); } }
【注意:必須要將接口注冊綁定到我們的核心配置文件中!】
關於@Param( )注解
基本類型的參數或者String類型,需要加上
引用類型不需要加
如果只有一個基本類型的話,可以忽略,但是建議大家都加上
我們在SQL中引用的就是我們這里的@Param()中設定的屬性名
#{} 和 ${}區別
(1)n#{}表示一個占位符號,通過#{}可以實現preparedStatement向占位符中設置值,自動進行java類型和jdbc類型轉換,#{}可以有效防止sql注入。#{}可以接收簡單類型值或pojo屬性值。如果parameterType傳輸單個簡單類型值,#{}括號中可以是value或其它名稱。
(2)n<span id="MathJax-Span-2" class="mrow"><span id="MathJax-Span-3" class="texatom"><span id="MathJax-Span-4" class="mrow"><span id="MathJax-Span-5" class="texatom"><span id="MathJax-Span-6" class="mrow"><span id="MathJax-Span-7" class="mo">表<span id="MathJax-Span-8" class="texatom"><span id="MathJax-Span-9" class="mrow"><span id="MathJax-Span-10" class="mo">示<span id="MathJax-Span-11" class="texatom"><span id="MathJax-Span-12" class="mrow"><span id="MathJax-Span-13" class="mo">拼<span id="MathJax-Span-14" class="texatom"><span id="MathJax-Span-15" class="mrow"><span id="MathJax-Span-16" class="mo">接<span id="MathJax-Span-17" class="mi">s<span id="MathJax-Span-18" class="mi">q<span id="MathJax-Span-19" class="mi">l<span id="MathJax-Span-20" class="texatom"><span id="MathJax-Span-21" class="mrow"><span id="MathJax-Span-22" class="mo">串<span id="MathJax-Span-23" class="texatom"><span id="MathJax-Span-24" class="mrow"><span id="MathJax-Span-25" class="mo">,<span id="MathJax-Span-26" class="texatom"><span id="MathJax-Span-27" class="mrow"><span id="MathJax-Span-28" class="mo">通<span id="MathJax-Span-29" class="texatom"><span id="MathJax-Span-30" class="mrow"><span id="MathJax-Span-31" class="mo">過表示拼接sql串,通過{}可以將parameterType傳入的內容拼接在sql中且不進行jdbc類型轉換,<span id="MathJax-Span-33" class="mrow"><span id="MathJax-Span-34" class="texatom"><span id="MathJax-Span-35" class="mrow"><span id="MathJax-Span-36" class="texatom"><span id="MathJax-Span-37" class="mrow"><span id="MathJax-Span-38" class="mo">可<span id="MathJax-Span-39" class="texatom"><span id="MathJax-Span-40" class="mrow"><span id="MathJax-Span-41" class="mo">以<span id="MathJax-Span-42" class="texatom"><span id="MathJax-Span-43" class="mrow"><span id="MathJax-Span-44" class="mo">接<span id="MathJax-Span-45" class="texatom"><span id="MathJax-Span-46" class="mrow"><span id="MathJax-Span-47" class="mo">收<span id="MathJax-Span-48" class="texatom"><span id="MathJax-Span-49" class="mrow"><span id="MathJax-Span-50" class="mo">簡<span id="MathJax-Span-51" class="texatom"><span id="MathJax-Span-52" class="mrow"><span id="MathJax-Span-53" class="mo">單<span id="MathJax-Span-54" class="texatom"><span id="MathJax-Span-55" class="mrow"><span id="MathJax-Span-56" class="mo">類<span id="MathJax-Span-57" class="texatom"><span id="MathJax-Span-58" class="mrow"><span id="MathJax-Span-59" class="mo">型<span id="MathJax-Span-60" class="texatom"><span id="MathJax-Span-61" class="mrow"><span id="MathJax-Span-62" class="mo">值<span id="MathJax-Span-63" class="texatom"><span id="MathJax-Span-64" class="mrow"><span id="MathJax-Span-65" class="mo">或<span id="MathJax-Span-66" class="mi">p<span id="MathJax-Span-67" class="mi">o<span id="MathJax-Span-68" class="mi">j<span id="MathJax-Span-69" class="mi">o<span id="MathJax-Span-70" class="texatom"><span id="MathJax-Span-71" class="mrow"><span id="MathJax-Span-72" class="mo">屬<span id="MathJax-Span-73" class="texatom"><span id="MathJax-Span-74" class="mrow"><span id="MathJax-Span-75" class="mo">性<span id="MathJax-Span-76" class="texatom"><span id="MathJax-Span-77" class="mrow"><span id="MathJax-Span-78" class="mo">值<span id="MathJax-Span-79" class="texatom"><span id="MathJax-Span-80" class="mrow"><span id="MathJax-Span-81" class="mo">,<span id="MathJax-Span-82" class="texatom"><span id="MathJax-Span-83" class="mrow"><span id="MathJax-Span-84" class="mo">如<span id="MathJax-Span-85" class="texatom"><span id="MathJax-Span-86" class="mrow"><span id="MathJax-Span-87" class="mo">果<span id="MathJax-Span-88" class="mi">p<span id="MathJax-Span-89" class="mi">a<span id="MathJax-Span-90" class="mi">r<span id="MathJax-Span-91" class="mi">a<span id="MathJax-Span-92" class="mi">m<span id="MathJax-Span-93" class="mi">e<span id="MathJax-Span-94" class="mi">t<span id="MathJax-Span-95" class="mi">e<span id="MathJax-Span-96" class="mi">r<span id="MathJax-Span-97" class="mi">T<span id="MathJax-Span-98" class="mi">y<span id="MathJax-Span-99" class="mi">p<span id="MathJax-Span-100" class="mi">e<span id="MathJax-Span-101" class="texatom"><span id="MathJax-Span-102" class="mrow"><span id="MathJax-Span-103" class="mo">傳<span id="MathJax-Span-104" class="texatom"><span id="MathJax-Span-105" class="mrow"><span id="MathJax-Span-106" class="mo">輸<span id="MathJax-Span-107" class="texatom"><span id="MathJax-Span-108" class="mrow"><span id="MathJax-Span-109" class="mo">單<span id="MathJax-Span-110" class="texatom"><span id="MathJax-Span-111" class="mrow"><span id="MathJax-Span-112" class="mo">個<span id="MathJax-Span-113" class="texatom"><span id="MathJax-Span-114" class="mrow"><span id="MathJax-Span-115" class="mo">簡<span id="MathJax-Span-116" class="texatom"><span id="MathJax-Span-117" class="mrow"><span id="MathJax-Span-118" class="mo">單<span id="MathJax-Span-119" class="texatom"><span id="MathJax-Span-120" class="mrow"><span id="MathJax-Span-121" class="mo">類<span id="MathJax-Span-122" class="texatom"><span id="MathJax-Span-123" class="mrow"><span id="MathJax-Span-124" class="mo">型<span id="MathJax-Span-125" class="texatom"><span id="MathJax-Span-126" class="mrow"><span id="MathJax-Span-127" class="mo">值<span id="MathJax-Span-128" class="texatom"><span id="MathJax-Span-129" class="mrow"><span id="MathJax-Span-130" class="mo">,可以接收簡單類型值或pojo屬性值,如果parameterType傳輸單個簡單類型值,{}括號中只能是value。
注:
(1)簡單類型就是不是自己定義的類型
(2)模糊查詢:'%${value}%' 不可漏掉單引號
他們之間的區別用最直接的話來說就是:#相當於對數據 加上 雙引號,$相當於直接顯示數據。
1、#對傳入的參數視為字符串,也就是它會預編譯,select * from user where name = #{name},比如我傳一個csdn,那么傳過來就是 select * from user where name = 'csdn';
2、<span id="MathJax-Span-132" class="mrow"><span id="MathJax-Span-133" class="texatom"><span id="MathJax-Span-134" class="mrow"><span id="MathJax-Span-135" class="mo">將<span id="MathJax-Span-136" class="texatom"><span id="MathJax-Span-137" class="mrow"><span id="MathJax-Span-138" class="mo">不<span id="MathJax-Span-139" class="texatom"><span id="MathJax-Span-140" class="mrow"><span id="MathJax-Span-141" class="mo">會<span id="MathJax-Span-142" class="texatom"><span id="MathJax-Span-143" class="mrow"><span id="MathJax-Span-144" class="mo">將<span id="MathJax-Span-145" class="texatom"><span id="MathJax-Span-146" class="mrow"><span id="MathJax-Span-147" class="mo">傳<span id="MathJax-Span-148" class="texatom"><span id="MathJax-Span-149" class="mrow"><span id="MathJax-Span-150" class="mo">入<span id="MathJax-Span-151" class="texatom"><span id="MathJax-Span-152" class="mrow"><span id="MathJax-Span-153" class="mo">的<span id="MathJax-Span-154" class="texatom"><span id="MathJax-Span-155" class="mrow"><span id="MathJax-Span-156" class="mo">值<span id="MathJax-Span-157" class="texatom"><span id="MathJax-Span-158" class="mrow"><span id="MathJax-Span-159" class="mo">進<span id="MathJax-Span-160" class="texatom"><span id="MathJax-Span-161" class="mrow"><span id="MathJax-Span-162" class="mo">行<span id="MathJax-Span-163" class="texatom"><span id="MathJax-Span-164" class="mrow"><span id="MathJax-Span-165" class="mo">預<span id="MathJax-Span-166" class="texatom"><span id="MathJax-Span-167" class="mrow"><span id="MathJax-Span-168" class="mo">編<span id="MathJax-Span-169" class="texatom"><span id="MathJax-Span-170" class="mrow"><span id="MathJax-Span-171" class="mo">譯<span id="MathJax-Span-172" class="texatom"><span id="MathJax-Span-173" class="mrow"><span id="MathJax-Span-174" class="mo">,<span id="MathJax-Span-175" class="mi">s<span id="MathJax-Span-176" class="mi">e<span id="MathJax-Span-177" class="mi">l<span id="MathJax-Span-178" class="mi">e<span id="MathJax-Span-179" class="mi">c<span id="MathJax-Span-180" class="mi">t<span id="MathJax-Span-181" class="mo">∗<span id="MathJax-Span-182" class="mi">f<span id="MathJax-Span-183" class="mi">r<span id="MathJax-Span-184" class="mi">o<span id="MathJax-Span-185" class="mi">m<span id="MathJax-Span-186" class="mi">u<span id="MathJax-Span-187" class="mi">s<span id="MathJax-Span-188" class="mi">e<span id="MathJax-Span-189" class="mi">r<span id="MathJax-Span-190" class="mi">w<span id="MathJax-Span-191" class="mi">h<span id="MathJax-Span-192" class="mi">e<span id="MathJax-Span-193" class="mi">r<span id="MathJax-Span-194" class="mi">e<span id="MathJax-Span-195" class="mi">n<span id="MathJax-Span-196" class="mi">a<span id="MathJax-Span-197" class="mi">m<span id="MathJax-Span-198" class="mi">e<span id="MathJax-Span-199" class="mo">=將不會將傳入的值進行預編譯,select∗fromuserwherename={name},比如我穿一個csdn,那么傳過來就是 select * from user where name=csdn;
3、#的優勢就在於它能很大程度的防止sql注入,而$則不行。
比如:用戶進行一個登錄操作,后台sql驗證式樣的:select * from user where username=#{name} and password = #{pwd},
如果前台傳來的用戶名是“wang”,密碼是 “1 or 1=1”,用#的方式就不會出現sql注入,而如果換成$方式,
sql語句就變成了 select * from user where username=wang and password = 1 or 1=1。這樣的話就形成了sql注入。
4、MyBatis排序時使用order by 動態參數時需要注意,用$而不是#
字符串替換
默認情況下,使用#{}格式的語法會導致MyBatis創建預處理語句屬性並以它為背景設置安全的值(比如?)。這樣做很安全,很迅速也是首選做法,有時你只是想直接在SQL語句中插入一個不改變的字符串。比如,像ORDER BY,你可以這樣來使用:
ORDER BY ${columnName}
這里MyBatis不會修改或轉義字符串。
重要:接受從用戶輸出的內容並提供給語句中不變的字符串,這樣做是不安全的。這會導致潛在的SQL注入攻擊,因此你不應該允許用戶輸入這些字段,或者通常自行轉義並檢查
9、Lombok
Lombok項目是一個Java庫,它會自動插入編輯器和構建工具中,Lombok提供了一組有用的注釋,用來消除Java類中的大量樣板代碼。僅五個字符(@Data)就可以替換數百行代碼從而產生干凈,簡潔且易於維護的Java類。
使用步驟:
1.在IDEA中安裝Lombok插件
2.在項目中導入lombok的jar包
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
3.在程序上加注解
@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
說明:
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String password; }

對於mybatis來說復雜的sql語句還是用配置文件來做,下面兩個點是多對一和一對多的介紹。推薦使用聯合嵌套查詢
10、多對一處理:mybatis-06模塊
多個學生一個老師;
alter table student ADD CONSTRAINT fk_tid foreign key (tid) references teacher(id)
數據庫表:
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=utf8INSERT 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');1. 測試環境搭建
- 導入lombok
- 新建實體類Teacher,Student
- 建立Mapper接口
- 建立Mapper.xml文件
- 在核心配置文件中綁定注冊我們的Mapper接口或者文件 【方式很多,隨心選】
- 測試查詢是否能夠成功
2.創建pojio實體類和Dao層要一一對應
Student實體類
package com.kuangshen.pojo; import lombok.Data; @Data public class Student { private int id; private String name; private Teacher teacher; }Teacher實體類
1 package com.kuangshen.pojo; 2 3 import lombok.Data; 4 5 @Data 6 public class Teacher { 7 private int id; 8 private String name; 9 }3.在resources創建跟com.tongda.dao包路徑要一致,確保xml和mapper接口同包下
Mapper接口和對應的配置文件
StudentMapper
package com.kuangshen.dao; import com.bupt.pojo.Student; import java.util.List; public interface StudentMapper { //查詢所有學生的信息,及其對應的老師的信息 public List<Student> getStudent(); public List<Student> getStudent2(); }
4.StudentMapper.xml和TeacherMapper.xml中
![]()
StudentMapper.xml
<?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.tongda.dao.StudentMapper"> <!--思路: 1. 查詢所有的學生信息 2. 根據查詢出來的學生的tid尋找特定的老師 (子查詢)--> <!--方式一:按照子查詢的方式--> <select id="getStudent" resultMap="StudentTeacher"> select * from mybatis.student; </select> <!--關聯resultMap="StudentTeacher" type=實體類名--> <resultMap id="StudentTeacher" type="Student"> <!--property指的是java中的字段屬性,column指的是查詢的數據庫中列表--> <result property="id" column="id"/> <result property="name" column="name"/> <!--復雜的屬性,我們需要單獨處理,對象:association 實體類中老師對象,集合:collection --> <!--關鍵點嵌套查詢:javaType是給property="teacher"賦值類型,在使用select=“getTeacher"嵌套查詢--> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="Teacher"> select * from teacher where id = #{id} </select> <!--方法二:按照結果嵌套查詢處理--> <select id="getStudent2" resultMap="StudentTeacher"> select s.id sid,s.name sname,t.name table_name 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 實體類中老師對象,集合:collection --> <association property="teacher" javaType="teacher"> <result property="name" column="tnmae"/> </association> </resultMap> </mapper>TeacherMapper.xml
<?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"> <!--configuration核心配置文件--> <mapper namespace="com.tongda.dao.TeacherMapper"> </mapper>5.mybatis-config.xml中
<!--每一個Mapper.xml都需要在Mybatis核心配置文件中注冊--> <!--綁定接口--> <mappers> <!--<mapper resource="com/tongda/dao/*Mapper.xml"/>--> <mapper class="com.tongda.dao.TeacherMapper"/> <mapper class="com.tongda.dao.StudentMapper"/> <!--<package name="com.tongda.dao"/>--> </mappers>6.測試
public class MyTest { public static void main(String[] args) { SqlSession sqlSession = MybatisUtils.getSqlSession(); TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacher(1); System.out.println(teacher); // sqlSession.commit(); sqlSession.close(); } }2. 按照查詢嵌套處理
其中最重要的就是兩種處理方式
方式一:按照查詢嵌套處理
<?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.tongda.dao.StudentMapper"> <!--思路: 1. 查詢所有的學生信息 2. 根據查詢出來的學生的tid尋找特定的老師 (子查詢)--> <!--方式一:按照子查詢的方式--> <select id="getStudent" resultMap="StudentTeacher"> select * from student; </select> <!--關聯resultMap="StudentTeacher" type=實體類名--> <resultMap id="StudentTeacher" type="Student"> <!--property指的是java中的字段屬性,column指的是查詢的數據庫中列表--> <result property="id" column="id"/> <result property="name" column="name"/> <!--復雜的屬性,我們需要單獨處理,對象:association 實體類中老師對象,集合:collection --> <!--關鍵點嵌套查詢:javaType是給property="teacher"賦值類型,在使用select=“getTeacher"嵌套查詢--> <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="Teacher"> select * from teacher where id = #{id} </select> </mapper>方式二:按照結果嵌套處理
<!--按照結果進行查詢--> <select id="getStudent2" resultMap="StudentTeacher2"> select s.id sid , s.name sname, 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="name" column="tname"></result> </association> </resultMap>回顧Mysql多對一查詢方式:
- 子查詢 (按照查詢嵌套)
- 聯表查詢 (按照結果嵌套)
11、一對多處理
一個老師多個學生;
對於老師而言,就是一對多的關系;
1. 環境搭建
實體類
@Data public class Student { private int id; private String name; //外鍵關聯語句,關聯teacher表 //KEY `fktid` (`tid`), // CONSTRAINT `fktid` FOREIGN KEY(`tid`) REFERENCES `teacher` (`id`) private int tid; }@Data public class Teacher { private int id; private String name; //一個老師擁有多個學生 private List<Student> students; }2. 按照結果嵌套嵌套處理
<?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"> <!--configuration核心配置文件--> <mapper namespace="com.tongda.dao.TeacherMapper"> <!--<select id="getTeacher" resultType="Teacher"> select * from teacher </select>--> <!--當結果集出現null,需要做映射--> <!--按結果嵌套查詢--> <select id="getTeacher" resultMap="TeacherStudent"> select s.id sid,s.name sname,t.name tname,t.id tid from student s,teacher t where s.tid = t.id and t.id = #{tid} </select> <!--mapper成對,類型=teacher--> <resultMap id="TeacherStudent" type="teacher"> <result property="id" column="tid"/> <result property="name" column="tname"/> <!--復雜的屬性,我們需要單獨處理 對象:association 集合:collection javaType=""指定屬性的類型! 集合中的泛型信息,我們使用ofType獲取 --> <!--property=實體類中屬性,ofType=實體類中的泛型--> <collection property="students" ofType="Student"> <!--Student 實體類屬性映射--> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap> </mapper>方式二:子查詢方式實現一對多
<!--先查老師--> <select id="getTeacher2" resultMap="TeacherStudent2"> select * from mybatis.teacher where id=#{tid} </select> <resultMap id="TeacherStudent2" type="Teacher"> <!--實體類中相同屬性與字段可省略,只寫不同--> <!--<result property="id" column="id"/>--> <!--<result property="name" column="name"/>--> <!--復雜的屬性,我們需要單獨處理 對象:association 集合:collection javaType=""指定屬性的類型! 集合中的泛型信息,我們使用ofType獲取 --> <!--property=實體類中屬性,ofType=實體類中的泛型--> <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/> </resultMap> <select id="getStudentByTeacherId" resultType="Student"> select * from mybatis.student where tid= #{tid} </select>小結
1.關聯 - association 【多對一】
2.集合 - collection 【一對多】
3.javaType & ofType
- JavaType用來指定實體類中的類型
- ofType用來指定映射到List或者集合中的pojo類型,泛型中的約束類型
注意點:
- 保證SQL的可讀性,盡量保證通俗易懂
- 注意一對多和多對一,屬性名和字段的問題
- 如果問題不好排查錯誤,可以使用日志,建議使用Log4j
面試高頻
- Mysql引擎
- InnoDB底層原理
- 索引
- 索引優化
12、動態SQL
什么是動態SQL:動態SQL就是根據不同的條件生成不同的SQL語句
所謂的動態SQL,本質上還是SQL語句,只是我們可以在SQL層面,去執行一個邏輯代碼
動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解
根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去
掉列表最后一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。之前用過 JSTL 或任何基於類 XML 語言的文本處理器,你對動態 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間了解大量的元素。借助功能強大的基於 OGNL 的表達式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
搭建環境
創建SQL表時,id因使用UUID,類型Varchar(50)
CREATE TABLE `blog`( `id` VARCHAR(50) NOT NULL 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`) )ENGINE=INNODB DEFAULT CHARSET=utf8創建一個基礎工程
1.導包
2.編寫配置文件
3.編寫實體類
@Data public class Blog { private String id; private String title; private String author; //用java.utils包,字段名駝峰,屬性與字段不一致 private Date createTime; private int views; }解決屬性名和字段不一致問題:
mybatis-config.xml中
mapUnderscoreToCamelCase 是否開啟自動駝峰命名規則(camel case)映射,即從經典數據庫列名 A_COLUMN 到經典 Java 屬性名 aColumn 的類似映射。 true, false False <!--日志配置--> <settings> <!--標准的日志工廠實現--> <setting name="logImpl" value="STDOUT_LOGGING"/> <!--<setting name="logImpl" value="LOG4J"/>--> <!--是否開啟自動駝峰命名規則(camel case)映射,即從經典數據庫列名 A_COLUMN 到經典 Java 屬性名 aColumn 的類似映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>編寫工具類IDutils
@SuppressWarnings("all")//抑制警告 public class IDutils { public static String getId(){ //自增ID是有序的,而UUID是隨機的 // 數據量非常大需要分庫,或者需要更好的安全性,那么使用UUID return UUID.randomUUID().toString().replaceAll("-",""); } @Test public void test(){ System.out.println(IDutils.getId()); System.out.println(IDutils.getId()); System.out.println(IDutils.getId()); } }4.編寫實體類對應Mapper接口和Mapper.xml文件
BlogMapper
public interface BlogMapper { int addBook(Blog blog); List<Blog> queryBlogIf(Map map); List<Blog> queryChoose(Map map); //更新博客 int updateBlog(Map map); }BlogMapper.xml
通過使用動態sql可以動態的編寫sql語句,並且動態sql可以自動去除多余的and和where和,等多余的內容。
<?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.bupt.dao.BlogMapper"> <insert id="addBook" parameterType="blog"> insert into blog(id,title,author,create_time,views) values (#{id},#{title},#{author},#{createTime},#{views}); </insert> <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> <select id="queryChoose" 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 view = #{views} </otherwise> </choose> </where> </select> <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> </mapper>測試
public class MyTest { @Test public void addInitBlog(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog = new Blog(); blog.setId(IDutils.getId()); blog.setTitle("mybatis如此簡單"); blog.setAuthor("狂神說"); blog.setCreateTime(new Date()); blog.setViews(9999); mapper.addBlog(blog); blog.setId(IDutils.getId()); blog.setTitle("java如此簡單"); mapper.addBlog(blog); blog.setId(IDutils.getId()); blog.setTitle("Spring如此簡單"); mapper.addBlog(blog); blog.setId(IDutils.getId()); blog.setTitle("微服務如此簡單"); mapper.addBlog(blog); sqlSession.close(); } }IF語句
需求:根據作者名字和博客名字來查詢博客!如果作者名字為空,那么只根據博客名字查詢,反之,則根據作者名來查詢
1、編寫接口類Mapper接口
//需求1 List<Blog> queryBlogIf(Map map);2、編寫SQL語句Mapper.xml文件
<!--需求1: 根據作者名字和博客名字來查詢博客! 如果作者名字為空,那么只根據博客名字查詢,反之,則根據作者名來查詢 select * from blog where title = #{title} and author = #{author} --> <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> </select>3、測試
@Test public void testQueryBlogIf(){ SqlSession session = MybatisUtils.getSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); HashMap<String, String> map = new HashMap<String, String>(); map.put("title","Mybatis如此簡單"); map.put("author","狂神說"); List<Blog> blogs = mapper.queryBlogIf(map); System.out.println(blogs); session.close(); }這樣寫我們可以看到,如果 author 等於 null,那么查詢語句為 select * from user where title=#{title},但是如果title為空呢?那么查詢語句為 select * from user where and author=#{author},這是錯誤的 SQL 語句,如何解決呢?請看下面的 where 語句!
Where&trim語句
修改上面的SQL語句;mapper.xml
<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 開頭的,則它會剔除掉。
trim, where, set
where 元素只會在子元素返回任何內容的情況下才插入 “WHERE” 子句。而且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除。
如果 where 元素與你期望的不太一樣,你也可以通過自定義 trim 元素來定制 where 元素的功能。比如,和 where 元素等價的自定義 trim 元素為:
<trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim>
前綴prefixOverrides屬性會忽略通過管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子會移除所有prefixOverrides屬性中指定的內容,並且插入prefix屬性中指定的內容。后綴suffixOerrides屬性
Set&trim語句
同理,上面的對於查詢 SQL 語句包含 where 關鍵字,如果在進行更新操作的時候,含有 set 關鍵詞,我們怎么處理呢?
1、編寫接口方法
int updateBlog(Map map);2、sql配置文件
<!--注意set是用的逗號隔開--> <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 session = MybatisUtils.getSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); HashMap<String, String> map = new HashMap<String, String>(); map.put("title","動態SQL"); map.put("author","秦疆"); map.put("id","9d6a763f5e1347cebda43e2a32687a77"); mapper.updateBlog(map); session.close(); }trim
這個例子中,set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)。
來看看與 set 元素等價的自定義 trim 元素吧:
<trim prefix="SET" suffixOverrides=","> ... </trim>注意,我們覆蓋了后綴值設置,並且自定義了前綴值。
choose語句:
choose:選擇(when:什么時候、otherwise:其他)
有時候,我們不想用到所有的查詢條件,只想選擇其中的一個,查詢條件有一個滿足即可,使用 choose 標簽可以解決此類問題,類似於 Java 的 switch 語句
1、編寫接口方法..
List<Blog> queryBlogChoose(Map map);2、sql配置文件
<!--where:條件 choose語句:選擇, when:什么時候--> <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 session = MybatisUtils.getSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); HashMap<String, Object> map = new HashMap<String, Object>(); map.put("title","Java如此簡單"); map.put("author","狂神說"); map.put("views",9999); List<Blog> blogs = mapper.queryBlogChoose(map); System.out.println(blogs); session.close(); }總結:所謂的動態SQL,本質還是SQL語句,只是我們可以在SQL層面,去執行一個邏輯代碼
練習:29道練習題實戰
SQL片段
有時候可能某個 sql 語句我們用的特別多,為了增加代碼的重用性,簡化代碼,我們需要將這些代碼抽取出來,然后使用時直接調用。
提取SQL片段:
<!-- sql id="名字隨意起" --> <sql id="if-title-author"> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql>引用SQL片段:
<select id="queryBlogIf" parameterType="map" resultType="blog"> select * from blog <where> <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace --> <include refid="if-title-author"></include> <!-- 在這里還可以引用其他的 sql 片段 --> </where> </select>注意:
①、最好基於 單表來定義 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where
Foreach
將數據庫中前三個數據的id修改為1,2,3;
需求:我們需要查詢 blog 表中 id 分別為1,2,3的博客信息
1、編寫接口
List<Blog> queryBlogForeach(Map map);2、編寫SQL語句
<select id="queryBlogForeach" parameterType="map" resultType="blog">select * from blog <where> <!-- collection:指定輸入對象中的集合屬性 item:每次遍歷生成的對象 open:開始遍歷時的拼接字符串 close:結束時拼接的字符串 separator:遍歷對象之間需要拼接的字符串 select * from blog where 1=1 and (id=1 or id=2 or id=3) --> <foreach collection="ids" item="id" open="and (" close=")" separator="or"> id=#{id} </foreach> </where> </select>3、測試
@Test public void testQueryBlogForeach(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); HashMap map = new HashMap(); List<Integer> ids = new ArrayList<Integer>(); ids.add(1); ids.add(2); ids.add(3); map.put("ids",ids); List<Blog> blogs = mapper.queryBlogForeach(map); System.out.println(blogs); sqlSession.close(); }小結:
動態SQL就是在拼接SQL語句,我們只要保證SQL的正確性,按照SQL的格式,去排列組合就可以了
建議:
現在Mysql中寫出完整的SQL,再對應的去修改成為我們的動態SQL實現通用即可!
其實動態 sql 語句的編寫往往就是一個拼接的問題,為了保證拼接准確,我們最好首先要寫原生的 sql 語句出來,然后在通過 mybatis 動態sql 對照着改,防止出錯。多在實踐中使用才是熟練掌握它的技巧。
動態SQL在開發中大量的使用,一定要熟練掌握!
13、緩存
13.1簡介
1、什么是緩存 [ Cache ]?
存在內存中的臨時數據。
將用戶經常查詢的數據放在緩存(內存)中,用戶去查詢數據就不用從磁盤上(關系型數據庫數據文件)查詢,從緩存中查詢,從而提高查詢效率,解決了高並發系統的性能問題。
2、為什么使用緩存?
減少和數據庫的交互次數,減少系統開銷,提高系統效率。
3、什么樣的數據能使用緩存?經常查詢並且不經常改變的數據。【可以使用緩存】
13.2 Mybatis緩存
Mybatis緩存
MyBatis包含一個非常強大的查詢緩存特性,它可以非常方便地定制和配置緩存。緩存可以極大的提升查詢效率。
MyBatis系統中默認定義了兩級緩存:一級緩存和二級緩存
默認情況下,只有一級緩存開啟。(SqlSession級別的緩存,也稱為本地緩存)
二級緩存需要手動開啟和配置,他是基於namespace級別的緩存。
為了提高擴展性,MyBatis定義了緩存接口Cache。我們可以通過實現Cache接口來自定義二級緩存
13.3 一級緩存
一級緩存
一級緩存也叫本地緩存:
與數據庫同一次會話期間查詢到的數據會放在本地緩存中。
以后如果需要獲取相同的數據,直接從緩存中拿,沒必須再去查詢數據庫;
13.4 測試
測試
1、在mybatis中加入日志,方便測試結果
<settings> <!--標准的日志2廠實現--> <setting nane="logImp1" value="STDOUT_LOGGING"/> </settings>2、編寫接口方法
//根據id查詢用戶 User queryUserById(@Param("id") int id);3、接口對應的Mapper文件
<select id="queryUserById" resultType="user"> select * from user where id = #{id} </select>映射mybatis-config.xml
<!--每一個Mapper.xml都需要在Mybatis核心配置文件中注冊--> <!--綁定接口--> <mappers> <!--<mapper resource="com/tongda/dao/*Mapper.xml"/>--> <!--<mapper class="com.tongda.dao.BlogMapperMapper"/>--> <!--<mapper class="com.tongda.dao.StudentMapper"/>--> <!--<mapper class="com.tongda.dao.BlogMapper"/>--> <!--<mapper resource="com/tongda/dao/BlogMapper.xml"/>--> <!--<package name="com.tongda.dao"/>--> <mapper class="com.tongda.dao.UserMapper"/> </mappers>4、測試
public class Mytest { @Test public void test1(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); User user2 = mapper.queryUserById(1); System.out.println(user2); System.out.println(user==user2); sqlSession.close(); } }
5、結果分析
13.5 一級緩存失效的四種情況
一級緩存失效的四種情況
1.查詢不同的東西
2.增刪改操作,可能會改變原來的數據,所以必定會刷新緩存!
3.查詢不同的Mapper.xml
4.手動清理緩存!一級緩存是SqlSession級別的緩存,是一直開啟的,我們關閉不了它;
**一級緩存失效情況:**沒有使用到當前的一級緩存,效果就是,還需要再向數據庫中發起一次查詢請求!
1、sqlSession不同
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); SqlSession session2 = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); UserMapper mapper2 = session2.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); User user2 = mapper2.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session.close(); session2.close(); }觀察結果:發現發送了兩條SQL語句!
結論:每個sqlSession中的緩存相互獨立
2、sqlSession相同,查詢條件不同
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); UserMapper mapper2 = session.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); User user2 = mapper2.queryUserById(2); System.out.println(user2); System.out.println(user==user2); session.close(); }觀察結果:發現發送了兩條SQL語句!很正常的理解
結論:當前緩存中,不存在這個數據
3、sqlSession相同,兩次查詢之間執行了增刪改操作!
增加方法
//修改用戶 int updateUser(Map map);編寫SQL
<update id="updateUser" parameterType="map"> update user set name = #{name} where id = #{id} </update>測試
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); HashMap map = new HashMap(); map.put("name","kuangshen"); map.put("id",4); mapper.updateUser(map); User user2 = mapper.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session.close(); }觀察結果:查詢在中間執行了增刪改操作后,重新執行了
結論:因為增刪改操作可能會對當前數據產生影響
4、sqlSession相同,手動清除一級緩存
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); session.clearCache();//手動清除緩存 User user2 = mapper.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session.close(); }小結:一級緩存默認是開啟的,只在一次SqlSession中有效,也就是拿到連接到關閉連接這個區間段!
一級緩存就是一個map
13.6 二級緩存
二級緩存:在SQL文件中添加
<mapper namespace="com.tongda.dao.UserMapper"> <!--開啟二級緩存--> <cache/>二級緩存也叫全局緩存,一級緩存作用域太低了,所以誕生了二級緩存
基於namespace級別的緩存,一個名稱空間,對應一個二級緩存;
工作機制
一個會話查詢一條數據,這個數據就會被放在當前會話的一級緩存中;
如果當前會話關閉了,這個會話對應的一級緩存就沒了;但是我們想要的是,會話關閉了,一級緩存中的數據被保存到二級緩存中;
新的會話查詢信息,就可以從二級緩存中獲取內容;
不同的mapper查出的數據會放在自己對應的緩存(map)中;
使用步驟
1、開啟全局緩存 【mybatis-config.xml】
<!--cacheEnabled該配置影響的所有映射器中配置的緩存的全局開關。true,false 默認true--> <setting name="cacheEnabled" value="true"/>2、去每個
mapper.xml中配置使用二級緩存,這個配置非常簡單;【xxxMapper.xml】<!--在當前xml中使用二級緩存--> <cache/>官方示例=====>查看官方文檔
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>這個更高級的配置創建了一個 FIFO 緩存,每隔 60 秒刷新,最多可以存儲結果對象或列表的 512 個引用,而且返回的對象被認為是只讀的,因此對它們進行修改可能會在不同線程中的調用者產生沖突。
3、代碼測試
1、問題:我們需要將實體類序列化!否則就會報錯!
Caused by:java.io.NotSerializableException:com.kuang.pojo.User
- 所有的實體類先實現序列化 implements Serializable 接口
@Data public class User implements Serializable { private int id; private String name; private String pwd; }
- 測試代碼
@Test public void testQueryUserById(){ SqlSession session = MybatisUtils.getSession(); SqlSession session2 = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); UserMapper mapper2 = session2.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); session.close(); User user2 = mapper2.queryUserById(1); System.out.println(user2); System.out.println(user==user2); session2.close(); }結論
只要開啟了二級緩存,我們在同一個Mapper中的查詢,可以在二級緩存中拿到數據
查出的數據都會被默認先放在一級緩存中
只有會話提交或者關閉以后,一級緩存中的數據才會轉到二級緩存中
13.7 緩存原理圖
13.10 EhCache
EhCache
第三方緩存實現–EhCache: 查看百度百科
Ehcache是一種廣泛使用的java分布式緩存,用於通用緩存;
要在應用程序中使用Ehcache,需要引入依賴的jar包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>在
UserMapper.xml中使用對應的緩存即可<mapper namespace = “org.acme.FooMapper” > <cache type = “org.mybatis.caches.ehcache.EhcacheCache” /> </mapper>編寫
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>合理的使用緩存,可以讓我們程序的性能大大提升!
14、Mybatis總結








