環境
-
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. 其他
-
-
-
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緩存實現!