【狂神說】Mybatis學習筆記(全)
Mybatis
環境
- JDK1.8
- Mysql5.7
- maven 3.6.3
- IDEA
回顧
- JDBC
- Mysql
- Java基礎
- Maven
- Junit
SSM框架:配置文件的最好的方式:看官網文檔
2 Mybatis
1、Mybatis簡介(2020-10-21)
1.1 什么是Mybatis
MyBatis 是一款優秀的持久層框架
MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集的過程
MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 實體類 【Plain Old Java Objects,普通的 Java對象】映射成數據庫中的記錄。
MyBatis 本是apache的一個開源項目ibatis, 2010年這個項目由apache 遷移到了google code,並且改名為MyBatis 。
2013年11月遷移到Github .
Mybatis官方文檔 : http://www.mybatis.org/mybatis-3/zh/index.html
GitHub : https://github.com/mybatis/mybatis-3
如何獲得Mybatis
-
maven倉庫:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
1.2 持久化
數據持久化
- 持久化就是將程序的數據在持久狀態和瞬時狀態轉化的過程
- 內存:斷電即失
- 數據庫(JDBC),io文件持久化
- 生活方面的例子:冷藏,罐頭
為什么需要持久化
- 不想丟掉一些對象
- 內存太貴
1.3 持久層
Dao層,Service層,Controller層
- 完成持久化工作的代碼塊
- 層界限十分明顯
1.4 為什么需要Mybatis
- 幫助程序員將數據存入到數據庫中
- 方便
- 傳統的JDBC代碼太復雜,簡化->框架->自動化
- 不用Mybatis也可以。更容易上手。技術沒有高低之分
- 優點:
- 簡單易學:本身就很小且簡單。沒有任何第三方依賴,最簡單安裝只要兩個jar文件+配置幾個sql映射文件就可以了,易於學習,易於使用,通過文檔和源代碼,可以比較完全的掌握它的設計思路和實現。
- 靈活:mybatis不會對應用程序或者數據庫的現有設計強加任何影響。sql寫在xml里,便於統一管理和優化。通過sql語句可以滿足操作數據庫的所有需求。
- 解除sql與程序代碼的耦合:通過提供DAO層,將業務邏輯和數據訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。sql和代碼的分離,提高了可維護性。
- 提供xml標簽,支持編寫動態sql。
- ......
- 最重要的一點,使用的人多!公司需要!
2、第一個Mybatis程序
思路流程:搭建環境-->導入Mybatis--->編寫代碼--->測試
2.1 搭建環境
搭建數據庫
CREATE DATABASE `mybatis`;
USE `mybatis`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user`(`id`,`name`,`pwd`) values (1,'狂神','123456'),(2,'張三','abcdef'),(3,'李四','987654');
新建項目
- 新建一個普通maven項目
- 刪除src目錄
- 導入maven依賴
<!--import dependencies-->
<dependencies>
<!--mysql driver-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--mybatis-->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/kuang/dao/userMapper.xml"/>
</mappers>
</configuration>
編寫mybatis工具類
package com.kuang.utils;
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.InputStream;
//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窗口-->
<mapper namespace="com.kuang.dao.UserMapper">
<!--select查詢語句-->
<select id="selectUser" resultType="com.kuang.pojo.User">
select * from user
</select>
</mapper>
2.4 注意點:
- org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDao is not known to the MapperRegistry.
MapperRegistry是什么?
核心配置文件中注冊mappers
<!-- 每一個Mapper.xml文件都需要在Mybatis核心配置文件中注冊-->
<mappers>
<mapper resource="com/kuang/dao/userMapper.xml"/>
</mappers>
- junit測試
public class UserDaoTest {
@Test
public void test() {
// 第一步:獲得SqlSession對象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方式一:getMapper
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
for (User user : userList) {
System.out.println(user);
}
//關閉SqlSession
sqlSession.close();
}
}
可能遇到的問題:
- 配置文件沒有注冊;
- 綁定接口錯誤;
- 方法名不對;
- 返回類型不對;
- Maven導出資源問題。
問題說明
可能出現問題說明:Maven靜態資源過濾問題
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
3、CRUD以及配置解析
3.1 namespace
namespace中的包名要和Dao/Mapper接口的包名一致!
3.2 select
選擇,查詢語句;
- id:就是對應的namespace中的方法名;
- resultType:Sql語句執行的返回值!
- parameterType:參數類型!
1、編寫接口
//根據id查詢用戶
User getUserById(int id);
2、編寫對應mapper中的sql語句
<select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
select * from mybatis.user where id = #{id}
</select>
3、測試
@Test
public void getUserById(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
//關閉SqlSession
sqlSession.close();
}
3.3 insert
我們一般使用insert標簽進行插入操作,它的配置和select標簽差不多!
需求:給數據庫增加一個用戶
1、在UserMapper接口中添加對應的方法
//添加一個用戶
int addUser(User user);
2、在UserMapper.xml中添加insert語句
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
3、測試
@Test
public void testAddUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User(5,"王五","zxcvbn");
int i = mapper.addUser(user);
System.out.println(i);
session.commit(); //提交事務,重點!不寫的話不會提交到數據庫
session.close();
}
注意點:增、刪、改操作需要提交事務!
3.4 upadte
我們一般使用update標簽進行更新操作,它的配置和select標簽差不多!
需求:修改用戶的信息
1、同理,編寫接口方法
//修改一個用戶
int updateUser(User user);
2、編寫對應的配置文件SQL
<update id="updateUser" parameterType="com.kuang.pojo.User">
update user set name=#{name},pwd=#{pwd} where id = #{id}
</update>
3、測試
@Test
public void testUpdateUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
user.setPwd("asdfgh");
int i = mapper.updateUser(user);
System.out.println(i);
session.commit(); //提交事務,重點!不寫的話不會提交到數據庫
session.close();
}
3.5 delete
我們一般使用delete標簽進行刪除操作,它的配置和select標簽差不多!
需求:根據id刪除一個用戶
1、同理,編寫接口方法
//根據id刪除用戶
int deleteUser(int id);
2、編寫對應的配置文件SQL
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
3、測試
@Test
public void testDeleteUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
int i = mapper.deleteUser(5);
System.out.println(i);
session.commit(); //提交事務,重點!不寫的話不會提交到數據庫
session.close();
}
小結:
- 所有的增刪改操作都需要提交事務!
- 接口所有的普通參數,盡量都寫上@Param參數,尤其是多個參數時,必須寫上!
- 有時候根據業務的需求,可以考慮使用map傳遞參數!
- 為了規范操作,在SQL的配置文件中,我們盡量將Parameter參數和resultType都寫上!
3.6 分析錯誤
- xml文件中注釋不能出現中文報錯,查看自己的是UTF-8還是GBK編碼,改成為相應的就行。
<?xml version="1.0" encoding="UTF-8" ?>
<?xml version="1.0" encoding="GBK" ?>
即可成功測試。
- 標簽不要匹配錯!
- resource綁定mapper,需要使用路徑!
- 程序配置文件必須符合規范!
- NullPointerException,沒有注冊到資源!
- maven資源沒有導出問題!
3.7 萬能Map
假設,我們的實體類,或者數據庫中的表,字段或者參數過多,我們應當考慮使用Map!
//萬能的Map
int addUser2(Map<String,Object> map);
<!--對象中的屬性,可以直接取出來 傳遞map的key-->
<insert id="addUser2" parameterType="map">
insert into mybatis.user (id,pwd) values (#{userid},#{password})
</insert>
@Test
public void addUser2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<String, Object>();
map.put("userid",4);
map.put("password","123321");
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
Map傳遞參數,直接在sql中取出key即可!【parameterType=“map”】
對象傳遞參數,直接在sql中取對象的屬性即可!【parameterType=“Object”】
只有一個基本類型參數的情況下,可以直接在sql中取到!
多個參數用Map,或者注解!
3.8 思考題
模糊查詢like語句該怎么寫?
第1種:在Java代碼中添加sql通配符。
string wildcardname = “%smi%”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like #{value}
</select>
第2種:在sql語句中拼接通配符,會引起sql注入
string wildcardname = “smi”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like "%"#{value}"%"
</select>
4 配置解析
1. 核心配置文件
- mybatis-config.xml
- Mybatis的配置文件包含了會深深影響MyBatis行為的設置和屬性信息。
configuration(配置)
properties(屬性)
settings(設置)
typeAliases(類型別名)
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環境配置)
environment(環境變量)
transactionManager(事務管理器)
dataSource(數據源)
databaseIdProvider(數據庫廠商標識)
mappers(映射器)
2. 環境配置(environments)
MyBatis 可以配置成適應多種環境
不過要記住:盡管可以配置多個環境,但每個 SqlSessionFactory 實例只能選擇一種環境。
Mybatis 默認的事務管理器是JDBC,連接池:POOLED
3. 屬性
我們可以通過properties屬性來引用配置文件
這些屬性可以在外部進行配置,並可以進行動態替換。你既可以在典型的 Java 屬性文件中配置這些屬性,也可以在 properties 元素的子元素中設置。 (db.properties)
編寫一個配置文件
driver = com.mysql.jdbc.Driver
url = "jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8""
username = root
password = root
在核心配置文件中引入
mybatis-config.xml (同時有的話,優先走外面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>
<!--引入外部配置文件-->
<!--<properties resource="db.properties">-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--每一個Mapper.xml文件都需要在Mybatis核心配置文件中注冊-->
<mappers>
<mapper resource="com/kuang/dao/UserMapper.xml"/>
</mappers>
</configuration>
-
可以直接引入外部文件
-
可以在其中增加一些屬性配置
-
如果兩個文件有同一個字段,優先使用外部配置文件的
4. 類型別名 typeAliases
- 類型別名可為 Java 類型設置一個縮寫名字。 它僅用於 XML 配置.
- 意在降低冗余的全限定類名書寫。
<!--可以給實體類起別名-->
<typeAliases>
<typeAlias type="com.kuang.pojo.User" alias="User"/>
</typeAliases>
也可以指定一個包,每一個在包 domain.blog
中的 Java Bean,在沒有注解的情況下,會使用 Bean 的首字母小寫的非限定類名來作為它的別名。 比如 domain.blog.Author
的別名為 author
,;若有注解,則別名為其注解值。見下面的例子:
<typeAliases>
<package name="com.kuang.pojo"/>
</typeAliases>
在實體類比較少的時候,使用第一種方式。
如果實體類十分多,建議用第二種掃描包的方式。
第一種可以DIY別名,第二種不行,如果非要改,需要在實體上增加注解。
@Alias("author")
public class Author {
...
}
5.設置
設置名 | 描述 | 有效值 | 默認 |
---|---|---|---|
cacheEnabled | 全局性地開啟或關閉所有映射器配置文件中已配置的任何緩存。 | true | false | true |
lazyLoadingEnabled | 延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 特定關聯關系中可通過設置 fetchType 屬性來覆蓋該項的開關狀態。 |
true | false | false |
logImpl | 指定 MyBatis 所用日志的具體實現,未指定時將自動查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未設置 |
6. 其他
- typeHandlers(類型處理器)
- objectFactory(對象工廠)
- plugins(插件)
- mybatis-generator-core
- mybatis-plus
- 通用mapper
7. 映射器 mappers
MapperRegistry:注冊綁定我們的Mapper文件;
方式一: [推薦使用]
<mappers>
<mapper resource="com/hou/dao/UserMapper.xml"/>
</mappers>
方式二:
<mappers>
<mapper class="com.hou.dao.UserMapper" />
</mappers>
- 接口和它的Mapper必須同名
- 接口和他的Mapper必須在同一包下
方式三:
<mappers>
<!--<mapper resource="com/hou/dao/UserMapper.xml"/>-->
<!--<mapper class="com.hou.dao.UserMapper" />-->
<package name="com.hou.dao" />
</mappers>
- 接口和它的Mapper必須同名
- 接口和他的Mapper必須在同一包下
8. 作用域和生命周期
生命周期和作用域是至關重要的,因為錯誤的使用會導致非常嚴重的並發問題
SqlSessionFactoryBuilder:
- 一旦創建了SqlSessionFactory,就不再需要它了
- 局部變量
SqlSessionFactory:
- 就是數據庫連接池。
- 一旦被創建就應該在應用的運行期間一直存在 ,沒有任何理由丟棄它或重新創建另一個實例 。 多次重建 SqlSessionFactory 被視為一種代碼“壞習慣”。
- 因此 SqlSessionFactory 的最佳作用域是應用作用域。
- 最簡單的就是使用單例模式或者靜態單例模式。
SqlSession:
- 每個線程都應該有它自己的 SqlSession 實例。
- 連接到連接池的請求!
- SqlSession 的實例不是線程安全的,因此是不能被共享的 ,所以它的最佳的作用域是請求或方法作用域。
- 用完之后趕緊關閉,否則資源被占用。
這里面的每一個Mapper就代表每一個具體業務!
5 解決屬性名和字段名不一致的問題
1 問題
數據庫中的字段
新建一個項目,拷貝之前,測試實體字段不一致的情況
User
package com.hou.pogo;
public class User {
private int id;
private String name;
private String password;
}
問題:
User{id=2, name='wang', password='null'}
解決方法:
核心配置文件
- 起別名
<select id="getUserById" resultType="User"
parameterType="int">
select id,name,pwd as password from mybatis.user where id = #{id}
</select>
- resultMap 結果集映射
<?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綁定一個對應的mapper接口-->
<mapper namespace="com.hou.dao.UserMapper">
<select id="getUserById" resultMap="UserMap" parameterType="int">
select * from mybatis.user where id = #{id}
</select>
<!--結果集映射-->
<resultMap id="UserMap" type="User">
<!--colunm 數據庫中的字段,property實體中的屬性-->
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<result column="pwd" property="password"></result>
</resultMap>
</mapper>
resultMap
元素是 MyBatis 中最重要最強大的元素。- ResultMap 的設計思想是,對簡單的語句做到零配置,對於復雜一點的語句,只需要描述語句之間的關系就行了。
<resultMap id="UserMap" type="User">
<!--colunm 數據庫中的字段,property實體中的屬性-->
<!--<result column="id" property="id"></result>-->
<!--<result column="name" property="name"></result>-->
<result column="pwd" property="password"></result>
</resultMap>
-
resultMap 元素是 MyBatis 中最重要最強大的元素。
-
ResultMap 的設計思想是,對簡單的語句做到零配置,對於復雜一點的語句,只需要描述語句之間的關系就行了。
-
ResultMap 的優秀之處——你完全可以不用顯式地配置它們。
-
如果這個世界總是這么簡單就好了。
6. 日志
1. 日志工廠
如果一個數據庫操作出現了異常,我們需要排錯。日志就是最好的助手。
曾經:sout,debug
現在:日志工廠
- SLF4J
- LOG4J [掌握]
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING [掌握]
- NO_LOGGING
具體使用哪一個,在設置中設定
STDOUT_LOGGING 標志日志輸出
mybatis-confi中
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
2. Log4j
-
先導包
pom.xml下
<dependencies> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
-
新建log4j.properties文件
### set log levels ###
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/hou.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>
<setting name="logImpl" value="LOG4J"/>
</settings>
- Log4j使用
package com.hou.dao;
import com.hou.pojo.User;
import com.hou.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;
public class UserDaoTest {
static Logger logger = Logger.getLogger(UserDaoTest.class);
@Test
public void test(){
// 獲得sqlsession對象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
// 1.執行 getmapper
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
logger.info("測試");
User user = userDao.getUserById(2);
System.out.println(user);
}catch(Exception e){
e.printStackTrace();
}finally{
//關閉
sqlSession.close();
}
}
@Test
public void testLog4j(){
logger.info("info:進入了testlog4j");
logger.debug("debug:進入了testlog4j");
logger.error("error:進入了testlog4j");
}
}
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" parameterType="map" resultMap="UserMap"> select * from user limit #{startIndex},#{pageSize} </select>
-
測試
@Test public void getUserByLimit(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Map<String, Integer> map = new HashMap<String, Integer>(); 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實現分頁
-
接口
//分頁2 List<User> getUserByRowBounds();
-
mapper.xml
<!--分頁查詢2--> <select id="getUserByRowBounds"> select * from user limit #{startIndex},#{pageSize} </select>
-
測試
@Test public void getUserByRow(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); //RowBounds實現 RowBounds rowBounds = new RowBounds(1, 2); //通過java代碼層面 List<User> userList = sqlSession.selectList ("com.hou.dao.UserMapper.getUserByRowBounds", null,rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
7.3 分頁插件
8 使用注解開發
8.1 面向接口開發
三個面向區別
- 面向對象是指,我們考慮問題時,以對象為單位,考慮它的屬性和方法
- 面向過程是指,我們考慮問題時,以一個具體的流程(事務過程)為單位,考慮它的實現;
- 接口設計與非接口設計是針對復用技術而言的,與面向對象(過程)不是一個問題,更多的體現就是對系統整體的架構;
8.2 使用注解開發
-
刪除 UserMapper.xml
-
UserMapper
package com.hou.dao; import com.hou.pojo.User; import org.apache.ibatis.annotations.Select; import java.util.List; public interface UserMapper { @Select("select * from user") List<User> getUsers(); }
-
核心配置 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--引入外部配置文件--> <properties resource="db.properties"/> <!--可以給實體類起別名--> <typeAliases> <typeAlias type="com.hou.pojo.User" alias="User"></typeAlias> </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> <mapper class="com.hou.dao.UserMapper"></mapper> </mappers> </configuration>
本質:反射機制
底層:動態代理
Mybatis詳細執行流程:
- Resource獲取全局配置文件
- 實例化SqlsessionFactoryBuilder
- 解析配置文件流XMLCondigBuilder
- Configration所有的配置信息
- SqlSessionFactory實例化
- trasactional事務管理
- 創建executor執行器
- 創建SqlSession
- 實現CRUD
- 查看是否執行成功
- 提交事務
- 關閉
8.3 注解CRUD
編寫接口
package com.kuang.dao;
import com.hou.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
@Select("select * from user")
List<User> getUsers();
//方法存在多個參數,所有的參數必須加@Param
@Select("select * from user where id = #{id}")
User getUserById(@Param("id") int id);
@Insert("insert into user (id, name, pwd) values" +
"(#{id},#{name},#{password})")
int addUser(User user);
@Update("update user set name=#{name}, pwd=#{password} " +
"where id=#{id}")
int updateUser(User user);
@Delete("delete from user where id=#{id}")
int deleteUser(@Param("id") int id);
}
MybatisUtile
package com.kuang.utils;
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;
//sqlSessionFactory --> sqlSession
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();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}
Test
【注意:我們必須要將接口注冊綁定到我們的核心配置文件中】
package com.hou.dao;
import com.hou.pojo.User;
import com.hou.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class UserDaoTest {
@Test
public void test(){
// 獲得sqlsession對象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
// 1.執行 getmapper
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
List<User> userList = userDao.getUsers();
for (User user : userList) {
System.out.println(user);
}
}catch(Exception e){
e.printStackTrace();
}finally{
//關閉
sqlSession.close();
}
}
@Test
public void getuserById(){
// 獲得sqlsession對象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
// 1.執行 getmapper
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
User user = userDao.getUserById(1);
System.out.println(user);
}catch(Exception e){
e.printStackTrace();
}finally{
//關閉
sqlSession.close();
}
}
@Test
public void addUser(){
// 獲得sqlsession對象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
// 1.執行 getmapper
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
userDao.addUser(new User(6, "kun","123"));
}catch(Exception e){
e.printStackTrace();
}finally{
//關閉
sqlSession.close();
}
}
@Test
public void updateUser(){
// 獲得sqlsession對象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
// 1.執行 getmapper
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
userDao.updateUser(new User(6, "fang","123"));
}catch(Exception e){
e.printStackTrace();
}finally{
//關閉
sqlSession.close();
}
}
@Test
public void deleteUser(){
// 獲得sqlsession對象
SqlSession sqlSession = MybatisUtils.getSqlSession();
try{
// 1.執行 getmapper
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
userDao.deleteUser(6);
}catch(Exception e){
e.printStackTrace();
}finally{
//關閉
sqlSession.close();
}
}
}
關於@Param( )注解
- 基本類型的參數或者String類型,需要加上
- 引用類型不需要加
- 如果只有一個基本類型的話,可以忽略,但是建議大家都加上
- 我們在SQL中引用的就是我們這里的@Param()中設定的屬性名
#{} 和 ${}
9 Lombok
Lombok項目是一個Java庫,它會自動插入編輯器和構建工具中,Lombok提供了一組有用的注釋,用來消除Java類中的大量樣板代碼。僅五個字符(@Data)就可以替換數百行代碼從而產生干凈,簡潔且易於維護的Java類。
使用步驟:
-
在IDEA中安裝Lombok插件
-
在項目中導入Lombok的jar包
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency>
-
在程序上加注解
@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
@Data:無參構造,get,set,toString,hashCode
在實體類上加注解
package com.kuang.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; @Data @AllArgsConstructor @NoArgsConstructor @ToString public class User2 { private int id; private String name; private String password; }
10 多對一處理
多對一:
- 多個學生,對應一個老師
- 對於學生而言,關聯–多個學生,關聯一個老師【多對一】
- 對於老師而言,集合–一個老師,有很多個學生【一對多】
SQL語句:
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');
搭建測試環境
-
IDEA安裝Lombok插件
-
引入Maven依賴
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.10</version> </dependency>
-
新建實體類Teacher,Student
@Data //GET,SET,ToString,有參,無參構造 public class Teacher { private int id; private String name; } @Data public class Student { private int id; private String name; //多個學生可以是同一個老師,即多對一 private Teacher teacher; }
-
編寫實體類對應的Mapper接口 【兩個】
無論有沒有需要,都應該寫上,以備后來只需!
public interface StudentMapper { } public interface TeacherMapper { }
-
編寫Mapper接口對應的 mapper.xml配置文件 【兩個】
無論有沒有需求,都應該寫上,以備后來之需!
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.dao.StudentMapper"> </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"> <mapper namespace="com.kuang.dao.TeacherMapper"> </mapper>
-
在核心配置文件中綁定注冊我們的Mapper接口或者文件!【方法很多,隨心選】
<!--綁定接口--> <mappers> <mapper class="com.kuang.dao.TeacherMapper"/> </mappers>
-
測試查詢是否能夠成功!
<!--TeaacherMapper--> @Select("select * from teacher where id = #{tid}") Teacher getTeacher(@Param("tid") int id);
<!--MyTest-->
package com.kuang.dao;
import com.kuang.pojo.Teacher;
import com.kuang.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
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.close();
}
}
按照查詢嵌套處理
-
給StudentMapper接口增加方法
//獲取所有學生及對應老師的信息 public List<Student> getStudents();
-
編寫對應打的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"> <mapper namespace="com.kuang.dao.StudentMapper"> <!-- 需求:獲取所有學生及對應老師的信息 思路: 1. 獲取所有學生的信息 2. 根據獲取的學生信息的老師ID->獲取該老師的信息 3. 思考問題,這樣學生的結果集中應該包含老師,該如何處理呢,數據庫中我們一般使用關聯查詢? 1. 做一個結果集映射:StudentTeacher 2. StudentTeacher結果集的類型為 Student 3. 學生中老師的屬性為teacher,對應數據庫中為tid。 多個 [1,...)學生關聯一個老師=> 一對一,一對多 4. 查看官網找到:association – 一個復雜類型的關聯;使用它來處理關聯查詢 --> <select id="getStudents" resultMap="StudentTeacher"> select * from student </select> <resultMap id="StudentTeacher" type="Student"> <!--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 = #{id} </select> </mapper>
-
編寫完畢去Mybatis配置文件中,注冊Mapper!
-
注意點說明:
<resultMap id="StudentTeacher" type="Student"> <!--association關聯屬性 property屬性名 javaType屬性類型 column在多的一方的表中的列名--> <association property="teacher" column="{id=tid,name=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 = #{id} and name = #{name} </select>
-
測試
@Test public void testGetStudents(){ SqlSession session = MybatisUtils.getSession(); StudentMapper mapper = session.getMapper(StudentMapper.class); List<Student> students = mapper.getStudents(); for (Student student : students){ System.out.println( "學生名:"+ student.getName() +"\t老師:"+student.getTeacher().getName()); } }
按照結果嵌套處理
除了上面這種方式,還有其他思路嗎?
我們還可以按照結果進行嵌套處理;
-
接口方法編寫
public List<Student> getStudents2();
-
編寫對應的mapper文件
<!-- 按查詢結果嵌套處理 思路: 1. 直接查詢出結果,進行結果集的映射 --> <select id="getStudents2" 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"> <id property="id" column="sid"/> <result property="name" column="sname"/> <!--關聯對象property 關聯對象在Student實體類中的屬性--> <association property="teacher" javaType="Teacher"> <result property="name" column="tname"/> </association> </resultMap>
-
去mybatis-config文件中注入【此處應該處理過了】
<mapper class="com.kuang.dao.StudentMapper"/>
-
測試
@Test public void testGetStudents2(){ SqlSession session = MybatisUtils.getSession(); StudentMapper mapper = session.getMapper(StudentMapper.class); List<Student> students = mapper.getStudents2(); for (Student student : students){ System.out.println( "學生名:"+ student.getName() +"\t老師:"+student.getTeacher().getName()); } }
小結
- 按照查詢進行嵌套處理就像SQL中的子查詢
- 按照結果進行嵌套處理就像SQL中的聯表查詢
回顧Mysql多對一查詢方式:
- 子查詢
- 聯表查詢
11 一對多處理
比如:一個老師擁有多個學生!
對於老師而言,就是一對多的關系!
環境搭建
-
環境搭建,和剛才一樣
實體類:@Data public class Student { private int id; private String name; private int tid; }
@Data public class Teacher { private int id; private String name; //一個老師擁有多個學生 private List<Student> students; }
..... 和之前一樣,搭建測試的環境!
按結果嵌套處理
-
TeacherMapper接口編寫方法
//獲取指定老師,及老師下的所有學生 public Teacher getTeacher(int id);
-
編寫接口對應的Mapper配置文件
<mapper namespace="com.kuang.mapper.TeacherMapper"> <!-- 思路: 1. 從學生表和老師表中查出學生id,學生姓名,老師姓名 2. 對查詢出來的操作做結果集映射 1. 集合的話,使用collection! JavaType和ofType都是用來指定對象類型的 JavaType是用來指定pojo中屬性的類型 ofType指定的是映射到list集合屬性中pojo的類型。 --> <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=#{id} </select> <resultMap id="TeacherStudent" type="Teacher"> <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> </mapper>
-
將Mapper文件注冊到MyBatis-config文件中
<mappers> <mapper resource="mapper/TeacherMapper.xml"/> </mappers>
-
測試
@Test public void testGetTeacher(){ SqlSession session = MybatisUtils.getSession(); TeacherMapper mapper = session.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacher(1); System.out.println(teacher.getName()); System.out.println(teacher.getStudents()); }
按查詢嵌套處理
-
TeacherMapper接口編寫方法
public Teacher getTeacher2(int id);
-
編寫接口對應的Mapper配置文件
<select id="getTeacher2" resultMap="TeacherStudent2"> select * from teacher where id = #{id} </select> <resultMap id="TeacherStudent2" 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>
-
將Mapper文件注冊到MyBatis-config文件中
-
測試
@Test public void testGetTeacher2(){ SqlSession session = MybatisUtils.getSession(); TeacherMapper mapper = session.getMapper(TeacherMapper.class); Teacher teacher = mapper.getTeacher2(1); System.out.println(teacher.getName()); System.out.println(teacher.getStudents()); }
小結
- 關聯-association
- 集合-collection
- 所以association是用於一對一和多對一,而collection是用於一對多的關系
- javaType和ofType都是用來指定對象類型的
- JavaType是用來指定pojo中屬性的類型
- ofType指定的是映射到list集合屬性中pojo的類型。
注意說明:
- 保證SQL的可讀性,盡量通俗易懂
- 根據實際要求,盡量編寫性能更高的SQL語句
- 注意屬性名和字段不一致的問題
- 注意一對多和多對一 中:字段和屬性對應的問題
- 盡量使用Log4j,通過日志來查看自己的錯誤
12 動態SQL
介紹
什么是動態SQL:動態SQL指的是根據不同的查詢條件 , 生成不同的Sql語句.
官網描述:
MyBatis 的強大特性之一便是它的動態 SQL。如果你有使用 JDBC 或其它類似框架的經驗,你就能體會到根據不同條件拼接 SQL 語句的痛苦。例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態 SQL 這一特性可以徹底擺脫這種痛苦。
雖然在以前使用動態 SQL 並非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射語句中的強大的動態 SQL 語言得以改進這種情形。
動態 SQL 元素和 JSTL 或基於類似 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多元素需要花時間了解。MyBatis 3 大大精簡了元素種類,現在只需學習原來一半的元素便可。MyBatis 采用功能強大的基於 OGNL 的表達式來淘汰其它大部分元素。
-------------------------------
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
-------------------------------
我們之前寫的 SQL 語句都比較簡單,如果有比較復雜的業務,我們需要寫復雜的 SQL 語句,往往需要拼接,而拼接 SQL ,稍微不注意,由於引號,空格等缺失可能都會導致錯誤。
那么怎么去解決這個問題呢?這就要使用 mybatis 動態SQL,通過 if, choose, when, otherwise, trim, where, set, foreach
等標簽,可組合成非常靈活的SQL語句,從而在提高 SQL 語句的准確性的同時,也大大提高了開發人員的效率。
搭建環境
新建一個數據庫表:blog
字段:id,title,author,create_time,views
CREATE TABLE `blog` (
`id` varchar(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客標題',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '創建時間',
`views` int(30) NOT NULL COMMENT '瀏覽量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
創建一個基礎工程
-
創建Mybatis基礎工程
-
IDutil工具類
public class IDUtil {
public static String genId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
-
實體類編寫 【注意set方法作用】
import java.util.Date; public class Blog { private String id; private String title; private String author; private Date createTime; private int views; //set,get.... }
-
編寫Mapper接口及xml文件
public interface BlogMapper { } <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.mapper.BlogMapper"> </mapper>
-
mybatis核心配置文件,下划線駝峰自動轉換
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--注冊Mapper.xml--> <mappers> <mapper resource="mapper/BlogMapper.xml"/> </mappers>
-
插入初始數據
編寫接口
//新增一個博客 int addBlog(Blog blog); sql配置文件 <insert id="addBlog" parameterType="blog"> insert into blog (id, title, author, create_time, views) values (#{id},#{title},#{author},#{createTime},#{views}); </insert>
初始化博客方法
@Test public void addInitBlog(){ SqlSession session = MybatisUtils.getSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = new Blog(); blog.setId(IDUtil.genId()); blog.setTitle("Mybatis如此簡單"); blog.setAuthor("狂神說"); blog.setCreateTime(new Date()); blog.setViews(9999); mapper.addBlog(blog); blog.setId(IDUtil.genId()); blog.setTitle("Java如此簡單"); mapper.addBlog(blog); blog.setId(IDUtil.genId()); blog.setTitle("Spring如此簡單"); mapper.addBlog(blog); blog.setId(IDUtil.genId()); blog.setTitle("微服務如此簡單"); mapper.addBlog(blog); session.close(); }
初始化數據完畢!
if
需求:根據作者名字和博客名字來查詢博客!如果作者名字為空,那么只根據博客名字查詢,反之,則根據作者名來查詢
-
編寫接口類
//需求1 List<Blog> queryBlogIf(Map map);
-
編寫SQL語句
<!--需求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>
-
測試
@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
修改上面的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 開頭的,則它會剔除掉。
choose語句
有時候,我們不想用到所有的查詢條件,只想選擇其中的一個,查詢條件有一個滿足即可,使用 choose 標簽可以解決此類問題,類似於 Java 的 switch 語句
-
編寫接口方法
List<Blog> queryBlogChoose(Map map);
-
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>
-
測試類
@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(); }
補充
MyBatis 提供了 choose 元素。if標簽是與(and)的關系,而 choose 是或(or)的關系。
choose標簽是按順序判斷其內部when標簽中的test條件出否成立,如果有一個成立,則 choose 結束。當 choose 中所有 when 的條件都不滿則時,則執行 otherwise 中的sql。類似於Java 的 switch 語句,choose 為 switch,when 為 case,otherwise 則為 default。
Set
同理,上面的對於查詢 SQL 語句包含 where 關鍵字,如果在進行更新操作的時候,含有 set 關鍵詞,我們怎么處理呢?
-
編寫接口方法
int updateBlog(Map map);
-
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>
-
測試
@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(); }
所謂的動態SQL,本質還是SQL語句,只是我們可以在SQL層面,去執行一個邏輯代碼
SQL片段
有時候可能某個 sql 語句我們用的特別多,為了增加代碼的重用性,簡化代碼,我們需要將這些代碼抽取出來,然后使用時直接調用。
提取SQL片段:
<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
- 動態 SQL 的另一個常見使用場景是對集合進行遍歷(尤其是在構建 IN 條件語句的時候)。
- foreach 元素的功能非常強大,它允許你指定一個集合,聲明可以在元素體內使用的集合項(item)和索引(index)變量。它也允許你指定開頭與結尾的字符串以及集合項迭代之間的分隔符。這個元素也不會錯誤地添加多余的分隔符,看它多智能!
- 提示你可以將任何可迭代對象(如 List、Set 等)、Map 對象或者數組對象作為集合參數傳遞給 foreach。當使用可迭代對象或者數組時,index 是當前迭代的序號,item 的值是本次迭代獲取到的元素。當使用 Map 對象(或者 Map.Entry 對象的集合)時,index 是鍵,item 是值。
將數據庫中前三個數據的id修改為1,2,3;
需求:我們需要查詢 blog 表中 id 分別為1,2,3的博客信息
-
編寫接口
List<Blog> queryBlogForeach(Map map);
-
編寫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>
-
測試
@Test public void testQueryBlogForeach(){ SqlSession session = MybatisUtils.getSession(); BlogMapper mapper = session.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); session.close(); }
小結
其實動態 sql 語句的編寫往往就是一個拼接的問題,為了保證拼接准確,我們最好首先要寫原生的 sql 語句出來,然后在通過 mybatis 動態sql 對照着改,防止出錯。多在實踐中使用才是熟練掌握它的技巧。
13 緩存
什么是緩存
- 存在內存中的臨時數據。
- 將用戶經常查詢的數據放在緩存(內存)中,用戶去查詢數據就不用從磁盤上(關系型數據庫查詢文件)查詢,從緩存中查詢,從而提高查詢效率,解決了高並發系統的性能問題。
為什么使用緩存
- 減少和數據庫的交互次數,減少系統開銷,提高系統效率。
什么樣的數據能使用緩存
- 經常查詢並且不經常改變的數據。【可以使用緩存】
Mybatis緩存
MyBatis包含一個非常強大的查詢緩存特性,它可以非常方便地定制和配置緩存。緩存可以極大的提升查詢效率。
MyBatis系統中默認定義了兩級緩存:一級緩存和二級緩存
默認情況下,只有一級緩存開啟。(SqlSession級別的緩存,也稱為本地緩存)
二級緩存需要手動開啟和配置,他是基於namespace級別的緩存。
為了提高擴展性,MyBatis定義了緩存接口Cache。我們可以通過實現Cache接口來自定義二級緩存
一級緩存
一級緩存也叫本地緩存:
與數據庫同一次會話期間查詢到的數據會放在本地緩存中。
以后如果需要獲取相同的數據,直接從緩存中拿,沒必須再去查詢數據庫;
測試
-
在mybatis中加入日志,方便測試結果
-
編寫接口方法
//根據id查詢用戶
User queryUserById(@Param("id") int id);
- 接口對應的Mapper文件
<select id="queryUserById" resultType="user">
select * from user where id = #{id}
</select>
- 測試
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.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);
session.close();
}
- 結果分析
一級緩存失效的四種情況
一級緩存是SqlSession級別的緩存,是一直開啟的,我們關閉不了它;
一級緩存失效情況:沒有使用到當前的一級緩存,效果就是,還需要再向數據庫中發起一次查詢請求!
- 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中的緩存相互獨立
- 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語句!很正常的理解
結論:當前緩存中,不存在這個數據
- 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();
}
一級緩存就是一個map
二級緩存
二級緩存也叫全局緩存,一級緩存作用域太低了,所以誕生了二級緩存
基於namespace級別的緩存,一個名稱空間,對應一個二級緩存;
工作機制
一個會話查詢一條數據,這個數據就會被放在當前會話的一級緩存中;
如果當前會話關閉了,這個會話對應的一級緩存就沒了;但是我們想要的是,會話關閉了,一級緩存中的數據被保存到二級緩存中;
新的會話查詢信息,就可以從二級緩存中獲取內容;
不同的mapper查出的數據會放在自己對應的緩存(map)中;
使用步驟
- 開啟全局緩存 【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
- 去每個mapper.xml中配置使用二級緩存,這個配置非常簡單;【xxxMapper.xml】
<cache/>
官方示例=====>查看官方文檔
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個更高級的配置創建了一個 FIFO 緩存,每隔 60 秒刷新,最多可以存儲結果對象或列表的 512 個引用,而且返回的對象被認為是只讀的,因此對它們進行修改可能會在不同線程中的調用者產生沖突。
- 代碼測試
所有的實體類先實現序列化接口
測試代碼
@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中的查詢,可以在二級緩存中拿到數據
查出的數據都會被默認先放在一級緩存中
只有會話提交或者關閉以后,一級緩存中的數據才會轉到二級緩存中
緩存原理圖
自定義緩存-ehcache(可以了解)
Ehcache是一種廣泛使用的開源Java分布式緩存,主要面向通用緩存。
要在程序中使用ehcache,先要導包!
在mapper中指定使用我們的ehcache緩存實現!
目前:Redis數據庫來做緩存!K-V