Mybatis的基本概念和基本使用
說明:學習筆記是對B站狂神說的Mybatis視頻的整理和總結。
參考資料:mybatis中文文檔
創建第一個Mybatis項目
主要步驟:
- 導入依賴
- 編寫Mybatis核心配置文件
- 編寫工具類,即獲取 SqlSession
- 編寫實體類、接口、映射SQL的XML文件
- 測試功能
1、導入依賴
首先創建一個 Maven 項目,並在 pom.xml 文件中導入下面的依賴。
<dependencies>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2、編寫Mybatis核心配置文件
在resources文件夾下創建mybatis-config.xml文件作爲Mybatis的核心配置文件。
- XML 配置文件中包含了對 MyBatis 系統的核心設置,包括獲取數據庫連接實例的數據源(DataSource)以及決定事務作用域和控制方式的事務管理器(TransactionManager)
- dao包下的每一個Mapper接口都需要在Mybatis核心配置文件中註冊。
<?xml version="1.0" encoding="GBK" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--核心配置文件-->
<configuration>
<!--可以配置多套環境,默認是development的環境-->
<environments default="development">
<environment id="development">
<!--JDBC的事務管理-->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--每一個Mapper都需要在Mybatis核心配置文件中註冊-->
<mappers>
<mapper resource="com/xlq/dao/UserMapper.xml"/>
</mappers>
</configuration>
3、編寫工具類,即獲取 SqlSession
每個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的實例爲核心的。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或一個預先配置的 Configuration 實例來構建出 SqlSessionFactory 實例。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 使用Mybatis的第一步,獲取sqlSessionFactory對象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
據 SqlSessionFactory 來獲得 SqlSession 的實例。SqlSession 提供了在數據庫執行 SQL 命令所需的所有方法。你可以通過 SqlSession 實例來直接執行已映射的 SQL 語句。
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
總結:
- XML配置文件→SqlSessionFactoryBuilder→SqlSessionFactory→SqlSession 注:中間步驟有所省略,詳細參考Mybatis的執行流程。
- SqlSessionFactory和SqlSession的關係:SqlSessionFactory可以理解成一個連接池,且全局環境下只需要一個連接池就夠了,SqlSession可以理解出一個數據庫連接,連接可以有多個,但用完之後一定要記得關閉,不然會一直佔用資源。
4、編寫實體類、接口、映射SQL的XML文件
實體類:
public class User {
// 與數據庫中的字段一一對應
private int id;
private String name;
private String pwd;
// 構造方法,get方法,set方法,toString方法這裏省略
}
接口:
public interface UserMapper {
// 查詢全部用戶
List<User> getUserList();
}
映射SQL的XML文件:
<?xml version="1.0" encoding="GBK" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace就是綁定一個對應的Mapper接口-->
<mapper namespace="com.xlq.dao.UserDao">
<!--select查詢語句-->
<select id="getUserList" resultType="com.xlq.pojo.User">
select * from test.user
</select>
</mapper>
5、測試功能
@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();
}
測試過程中遇到的錯誤:
錯誤1:org.apache.ibatis.binding.BindingException: Type interface com.xlq.dao.UserDao is not known to the MapperRegistry.
分析:每一個Mapper都需要在Mybatis核心配置文件中註冊。在mybatis-config.xml 中加入如下注冊:
<mappers>
<mapper resource="com/xlq/dao/UserDao"/>
</mappers>
錯誤2:java.lang.ExceptionInInitializerError
分析:這是資源導出失敗的問題,即UserMapper.xml被過濾掉了。maven由於它的約定大於配置,配置文件可能會出現無法被導出或者失效的問題。解決方法是在maven的配置文件pom.xml中加入下面的資源過濾規則:
<!--在build中配置resources, 來防止我們資源導出失敗的問題-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
可能會發生的問題:
- 配置文件沒有註冊;
- 綁定接口錯誤;
- 方法名不對;
- 返回類型不對;
- Maven導出資源問題。
補充:MyBatis的詳細執行流程
CURD-增刪改查
基本步驟:
- 編寫接口
- 編寫對應的mapper.xml中的sql語句
- 測試(注意:增刪改需要提交事務sqlSession.commit())
CURD示例:
<!--namespace就是綁定一個對應的Mapper接口-->
<mapper namespace="com.xlq.mapper.UserMapper">
<!--select查詢語句-->
<select id="getUserList" resultType="com.xlq.pojo.User">
select * from test.user
</select>
<select id="getUserById" parameterType="int" resultType="com.xlq.pojo.User">
select * from test.user where id=#{id}
</select>
<insert id="addUser" parameterType="com.xlq.pojo.User">
insert into test.user(id, name, pwd) values (#{id}, #{name}, #{pwd})
</insert>
<update id="updateUser" parameterType="com.xlq.pojo.User">
update test.user set name=#{name}, pwd=#{pwd} where id=#{id}
</update>
<delete id="deleteUser" parameterType="int">
delete from test.user where id=#{id}
</delete>
</mapper>
1、標籤的說明:
<mapper>
標籤的參數:- namespace綁定一個Mapper接口的全限定名。
- sql語句的標籤
<select><insert><update><delete>
的參數說明:- id:對應的是namespace中的方法名。
- resultType:SQL語句執行的返回值。
- parameterType:參數類型。
2、參數傳遞的方式
- Map傳遞參數,直接在sql中取出key即可。
- 對象傳遞參數,直接在sql中取對象的屬性即可!
- 只有一個基本類型參數的情況下,可以直接在sql中取到。
- 多個參數用Map,或者註解。
關於Map傳參:適用於實體類或者數據庫中的表字段非常多的情況。示例如下:
int addUser2(Map<String, Object> map);
<!--通過map來傳遞數據,適用於字段非常多的情況。傳遞map的key-->
<insert id="addUser2" parameterType="map">
insert into test.user(id, name, pwd) values (#{userid}, #{name}, #{password})
</insert>
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", 123);
map.put("name", "趙四");
mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
3、模糊查詢LIKE
Java代碼執行的時候,用戶傳遞通配符。但是可能會存在SQL注入的問題,即用戶的非法輸入改變了SQL語句的含義。
List<User> userList = mapper.getUserLike("%李%");
另一種方式,在sql拼接中使用通配符,這樣可以限定用戶的輸入。
<select id="getUserLike" resultType="com.xlq.pojo.User">
select * from test.user where name Like "%"#{value}"%"
</select>
配置解析
官網資料:Mybatis配置
mybatis-config.xml文件
MyBatis 的配置文件包含了會深深影響 MyBatis 行爲的設置和屬性信息。 配置文檔的頂層結構如下:
- configuration(配置)
- properties(屬性)
- settings(設置)
- typeAliases(類型別名)
- typeHandlers(類型處理器)
- objectFactory(對象工廠)
- plugins(插件)
- environments(環境配置)
- environment(環境變量)
- transactionManager(事務管理器)
- dataSource(數據源)
- environment(環境變量)
- databaseIdProvider(數據庫廠商標識)
- mappers(映射器)
需要掌握:屬性、設置、類型別名、環境配置、映射器。
1 環境配置(environments)
MyBatis 可以配置成適應多種環境,儘管可以配置多個環境,但每個 SqlSessionFactory 實例只能選擇一種環境。
Mybatis默認的事務管理器是JDBC,默認的數據源:連接池POOLED
2 屬性(properties)
我們可以通過properties屬性來實現引用配置文件。
這些屬性可以在外部進行配置,並可以進行動態替換。你既可以在典型的 Java 屬性文件中配置這些屬性,也可以在 properties 元素的子元素中設置。
編寫配置文件db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
在mybatis-config.xml中引入外部配置文件,將外部配置文件中設置好的屬性就可以在整個配置文件中用來替換需要動態配置的屬性值。
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<!--在環境中動態配置數據源-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
注意點:
- 可以直接引入外部配置文件
- 可以在其中增加一些屬性配置
- 如果出現同名的屬性,優先使用外部配置文件的屬性。
3 設置(settings)
這是 MyBatis 中極爲重要的調整設置,它們會改變 MyBatis 的運行時行爲。
三個關鍵設置:
4 類型別名(typeAliases)
類型別名可爲 Java 類型設置一個縮寫名字。 它僅用於 XML 配置,意在降低冗餘的全限定類名書寫。
第一種方式:直接指定某個類及其別名是什麼。
<typeAliases>
<typeAlias type="com.xlq.pojo.User" alias="User"/>
</typeAliases>
第二種方式:指定一個包名,MyBatis 會在包名下面搜索需要的 Java Bean(也就是指某個類)。在沒有註解的情況下,會使用 Bean 的首字母小寫的非限定類名來作爲它的別名;若有註解,則別名爲其註解值。
<typeAliases>
<package name="com.xlq.pojo"/>
</typeAliases>
5 映射器(mappers)
MapperRegistry:註冊綁定我們的Mapper文件。
主要有三種方式:
方式一:使用相對於類路徑的資源引用【推薦】
<mappers>
<mapper resource="com/xlq/dao/UserMapper.xml"/>
</mappers>
方式二:使用映射器接口實現類的完全限定類名
<mappers>
<mapper class="com.xlq.dao.UserMapper"/>
</mappers>
方式三:將包內的映射器接口實現全部註冊爲映射器
<mappers>
<package name="com.xlq.dao"/>
</mappers>
方式二和方式三的注意點:
- 接口和它的Mapper配置文件必須同名!
- 接口和它的Mapper配置文件必須在同一個包下!
6 生命週期和作用域
不同作用域和生命週期類別是至關重要的,因爲錯誤的使用會導致非常嚴重的併發問題。
SqlSessionFactoryBuilder:
- 一旦創建了 SqlSessionFactory,就不再需要它了
- 局部變量
SqlSessionFactory:
- 可以理解爲:數據庫連接池。
- SqlSessionFactory 一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建另一個實例。
- SqlSessionFactory 的最佳作用域是應用作用域。
- 最簡單的就是使用單例模式或者靜態單例模式。
SqlSession:
- 可以理解爲:連接到連接池的一個請求;
- SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。
- 用完之後需要趕緊關閉,否則資源被佔用。
XML映射
問題引入:當實體類中的屬性和數據庫中的字段名稱不一致時,類型處理器無法找到對應的映射,所以結果爲null。比如實體類User中的屬性是password,數據庫中的字段爲pwd.
最簡單暴力的解決方法:在SQL語句中寫別名。
<select id="getUserList" resultMap="User">
select id, name, pwd as password from test.user
<!--User是實體類,test是數據庫,user是表名,id name pwd是表中的字段-->
</select>
結果映射(resultMap)
要點:
- resultMap 元素是 MyBatis 中最重要最強大的元素。
- resultMap 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。
- resultMap 的優秀之處——你完全可以不用顯式地配置它們。
<resultMap id="userMap" type="User">
<!--對於id name不需要顯示配置,只需要配置pwd就可以了,這就是resultMap的優秀之處-->
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserList" resultMap="userMap">
select * from test.user
</select>
日誌
Mybatis支持的日誌包括:
- SLF4J
- LOG4J【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING【掌握】
- NO_LOGGING
其中STDOUT_LOGGING是Mybatis的標準日誌輸出。
在mybatis-config.xml中加入如下配置:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
IDEA的控制檯中日誌輸出內容部分如下:
log4j
什麼是log4j?有何特點?
- log4j是Apache的一個開源項目,通過使用log4j,我們可以控制日誌信息輸送的目的地是控制檯、文件、GUI組件。
- 我們也可以控制每一條日誌的輸出格式。
- 通過定義每一條日誌信息的級別,我們能夠更加細緻地控制日誌的生成過程。
- 這些可以通過一個配置文件來靈活地進行配置,而不需要修改應用的代碼。
第一步:添加依賴。
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
第二步:配置
配置log4j的配置文件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=./logInfo/xlq.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=DUBUG
log4j.logger.java.sql.Statement=DUBUG
log4j.logger.java.sql.ResultSet=DUBUG
log4j.logger.java.sql.PreparedStatement=DUBUG
第三步:在mybatis核心配置文件中設置日誌爲log4j
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
第四步:在項目中使用log4j,產生日誌文件。
- 在要使用log4j的類中,導入包:
import org.apache.log4j.Logger;
- 生成日誌對象,參數爲當前類的class
static Logger logger = Logger.getLogger(UserMapperTest.class);
- 日誌級別:
logger.info("info:進入了testLog4j方法");
logger.debug("debug:進入了testLog4j方法");
logger.error("error:進入了testLog4j方法");
分頁
爲什麼要分頁?減少數據量的處理量。
使用Mybatis實現分頁的功能,核心在於SQL,limit子句實現分頁的功能。
SQL語句:
// 如果只給定一個參數,它表示返回最大的記錄行數目。換句話說,LIMIT n 等價於 LIMIT 0,n:
mysql> SELECT * FROM table LIMIT 5; //檢索前 5 個記錄行
// 如果給定兩個參數,第一個參數指定第一個返回記錄行的偏移量,第二個參數指定返回記錄行的最大數目。
mysql> SELECT * FROM table LIMIT 5,10; //檢索記錄行6-15
Mapper.xml
<!--分頁-->
<select id="getUserListLimit" parameterType="map" resultMap="userMap">
select * from test.user limit #{bias}, #{num}
</select>
測試:
@Test
public void testLimit() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("bias", 2);
map.put("num", 2);
List<User> userList = userMapper.getUserListLimit(map);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
第二種思路:不使用SQL來實現,而是通過Java代碼層面來實現分頁(瞭解)
RowBounds類 -> SqlSession.selectList()
註解開發
面向接口編程
面向對象編程 VS 面向接口編程
在真正的開發中,很多時候我們會選擇面向接口編程。根本原因:解耦。分層開發中,上層不用管具體的實現,大家都遵守共同的標準,使得開發變得容易,規範性更好。
對接口的理解:
- 接口是定義(規範、約束)與實現的分離。
- 接口的本身反映了系統設計人員對系統的抽象理解。
- 接口應有兩類:
- 第一類是對一個個體的抽象,它可對應爲一個抽象體(abstract class)。
- 第二類是對一個個體某一方面的抽象,即形成一個抽象面(interface)。
使用註解開發
1、在Mapper中使用註解,代替了XML文件了。
public interface UserMapper {
@Select("select * from user")
List<User> getUserList();
}
注意:簡單的SQL可以使用註解,複雜的SQL就力不從心了。
2、在Mybatis核心配置文件中綁定接口:
<!--綁定接口-->
<mappers>
<mapper class="com.xlq.dao.UserMapper"/>
</mappers>
3、測試–略
CURD
public interface UserMapper {
@Select("select * from user")
List<User> getUserList();
@Select("select * from user where id = #{uid}")
User getUserById(@Param("uid") int id);
@Insert("insert into user(id, name, pwd) values (#{id}, #{name}, #{pwd})")
void InsertUser(User user);
@Update("update user set name=#{name}, pwd=#{pwd} where id=#{id}")
void updateUser(User user);
@Delete("delete from user where id=#{uid}")
void deleteUser(@Param("uid") int id);
}
多對一和一對多
複雜查詢的準備工作
在MySQL數據庫中創建老師表和學生表,並且使用外鍵產生字段關聯。
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) NOT 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中做如下準備:
- 新建實體類:Teacher,Student,並生成構造方法,get和set方法。(使用lombok很方便,自己不太喜歡用)
- 建立對應的Mapper接口:TeacherMapper,StudentMapper
- 建立Mapper.xml文件
- 在mybatis核心配置文件中綁定註冊Mapper接口
- 測試環境配置是否正常
多對一關係
什麼叫多對一?
多個學生關聯一個老師,簡單來說就是多對一。具體在數據庫表中,學生表中有一個字段tid
對應的是老師表中的id
字段。
實體類:
public class Student {
private int id;
private String name;
// 學生需要關聯一個老師
private Teacher teacher;
}
public class Teacher {
private int id;
private String name;
}
MySQL中多對一的查詢方式:
- 子查詢
- 連表查詢
對應到XML中,子查詢對應【按照查詢嵌套處理】,連表查詢對應【按照結果嵌套處理】。也就是說用子查詢的思想去看【按照查詢嵌套處理】中的XML代碼,用連表查詢的思想去看【按照結果嵌套處理】的XML代碼。
1、按照查詢嵌套處理
<!--直接查詢student表,屬性teacher是null,因爲它是引用變量-->
<!--思路:
1.查詢所有的學生信息
2.根據查詢出來的學生的tid,尋找對應的老師(即子查詢)-->
<select id="getStudent" resultMap="StudentTeacher">
select * from test.student
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--複雜的屬性,我們需要單獨處理,對象:association 集合:collection-->
<association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select * from test.teacher where id=#{tid}
</select>
查詢出來的結果是:
Student{id=1, name='小明', teacher=Teacher{id=1, name=王老師}}
Student{id=2, name='小紅', teacher=Teacher{id=1, name=王老師}}
Student{id=3, name='小張', teacher=Teacher{id=1, name=王老師}}
Student{id=4, name='小李', teacher=Teacher{id=1, name=王老師}}
Student{id=5, name='小芳', teacher=Teacher{id=1, name=王老師}}
2、按照結果嵌套處理
<!--方式二:按照結果嵌套處理-->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id as sid, s.name as sname, t.name as tname
from test.student as s, test.teacher as 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"/>
</association>
</resultMap>
得到的結果是:
Student{id=1, name='小明', teacher=Teacher{id=0, name=王老師}}
Student{id=2, name='小紅', teacher=Teacher{id=0, name=王老師}}
Student{id=3, name='小張', teacher=Teacher{id=0, name=王老師}}
Student{id=4, name='小李', teacher=Teacher{id=0, name=王老師}}
Student{id=5, name='小芳', teacher=Teacher{id=0, name=王老師}}
注意和上面的結果的差別:Teacher的id都爲零,因爲代碼中沒有做映射。
一對多關係
什麼是一對多?
一個老師擁有多個學生,對於老師而言,就是一對多的關係。所以說一對多和多對一是站在不同的角度來看的。
實體類:
public class Student {
private int id;
private String name;
private int tid;
}
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
1、按結果嵌套查詢
<select id="getTeacher" parameterType="int" resultMap="teacherStudent">
select s.id as sid, s.name as sname, t.id as tid, t.name as tname
from teacher as t, student as s
where t.id=s.tid and t.id=#{tid}
</select>
<!--type="teacher"和ofType="student",可以用teacher和student是因爲取了別名-->
<resultMap id="teacherStudent" type="teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--複雜的屬性,我們需要單獨處理,對象:association 集合:collection-->
<!--javaType用於指定屬性的類型,ofType用於指定集合中的泛型信息-->
<collection property="students" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
查詢結果:
Teacher{id=1, name='王老師', students=[Student{id=1, name='小明', tid=1}, Student{id=2, name='小紅', tid=1}, Student{id=3, name='小張', tid=1}, Student{id=4, name='小李', tid=1}, Student{id=5, name='小芳', tid=1}]}
2、按查詢條件
<select id="getTeacher2" parameterType="int" resultMap="teacherStudent2">
select * from test.teacher where id=#{tid}
</select>
<resultMap id="teacherStudent2" type="teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" javaType="ArrayList" ofType="student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="student">
select * from test.student where tid=#{tid}
</select>
查詢結果和上面的一樣。
小結
- 關聯 - association 【多對一】
- 集合 - collection 【一對多】
- javaType和ofType區別:
- javaType用來指定實體類中屬性的類型。也就說實體類的某個屬性可能是引用類型,javaType就用來指定這個引用類型。注意:這個引用類型肯定是另外一個實體類,也在bean中註冊了。
- ofType用來指定映射到List或者集合中的實體類型,簡單理解就是泛型中的約束類型。
注意點:
- 保證SQL的可讀性,儘量保證通俗易懂
- 注意一對多和多對一中屬性名字和字段的問題
- 如果不好排查錯誤,可以使用日誌。
動態SQL
什麼是動態SQL?動態SQL就是指根據不同的條件生成不同的SQL語句。
環境搭建
環境搭建的過程包括:Mybatis核心配置、工具類、實體類、實體類的Mapper接口和Mapper.xml、在測試類中寫代碼往數據庫中插入數據。
MySQL中的blog表建表語句:
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
實體類:
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;// 屬性名和字段名不一致
private int views;
}
實體類的Mapper接口,暫時只包含一個方法,即往數據庫中插入數據。
public interface BlogMapper {
void insertBlog(Blog blog);
}
Mapper對應的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.xlq.dao.BlogMapper">
<insert id="insertBlog" parameterType="com.xlq.pojo.Blog">
insert into test.blog (id, title, author, create_time, views)
values (#{id}, #{title}, #{author}, #{createTime}, #{views})
</insert>
</mapper>
動態SQL常用標籤
最常用的標籤:if where set
1、IF
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from test.blog where
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
<!--注:if標籤之間是並列的關係,每一個都會去匹配-->
</select>
這樣會有一個問題:如果只有第二個<if>
標籤滿足,那麼SQL語句變成了select...where and author=#{author}
,where後面跟了and,顯然是不符合語法規範的。解決方法:
第一種:where後面添加1=1
。
select * from test.blog where 1=1
and title = #{title}
and author = #{author}
第二種:使用<where>
標籤,它會智能地去除and
。
2、choose(when、otherwise)
類似於Java中的switch-case語句,只選擇其中一個執行,如果沒有匹配到就會進入<otherwise>
標籤中。
3、where
where元素只會在至少有一個子元素的條件返回SQL子句的情況下,纔去插入“WHERE”子句,而且,若語句的開頭爲“AND”或“OR”,where元素也會將它們去除。
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from test.blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="views != null">
and views = #{views}
</if>
</where>
</select>
4、set
用於update語句中,<set>
元素會動態前置SET關鍵字,同時也會刪掉無關的逗號。
解釋:因爲<set>
標籤裏面用的是<if>
標籤,如果最後一個<if>
沒有匹配上而前面的匹配上了,SQL語句中就會遺留一個逗號。<set>
就可以刪除這個遺留的逗號。
瞭解:<where>
和<set>
有一個父標籤<trim>
。
5、foreach
遍歷集合。
<select id="selectBlogForeach" parameterType="map" resultType="blog">
select * from test.blog
<where>
<foreach collection="list" item="id"
open="(" separator="or" close=")">
id=#{id}
</foreach>
</where>
</select>
測試類:
@Test
public void test3() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
map.put("list", list);
List<Blog> blogs = mapper.selectBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
6、SQL片段
作用是實現代碼的複用。使用<sql>
標籤抽取公共的部分,在需要使用的地方使用<include>
標籤引用即可。
<sql id="if-title-views">
<if test="title != null">
title = #{title}
</if>
<if test="views != null">
and views = #{views}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from test.blog
<where>
<include refid="if-title-views"/>
</where>
</select>
小結:所謂的動態SQL,本質還是SQL語句,只是我們可以在SQL層面,去執行一個邏輯代碼,實現SQL的動態拼接。
緩存
緩存的概念
1、什麼是緩存?
- 存在內存中的臨時數據。
- 將用戶經常查詢的數據放在緩存中,用戶去查詢數據就不用從磁盤上(關係型數據庫數據文件)查詢,直接從緩存中查詢,從而提高查詢效率,解決了高併發系統的性能問題。
2、爲什麼要使用緩存?
減少和數據庫的交互次數,減少系統開銷,提高系統效率。
3、什麼樣的場景下使用緩存?
經常查詢並且不經常改變的數據。
Mybatis緩存
Mybatis系統中默認定義了兩極緩存:一級緩存和二級緩存。
- 默認情況下,只有一級緩存開啓(SqlSession級別的緩存,也稱爲本地緩存)
- 二級緩存需要手動開啓和配置,它是基於namespace級別的緩存。
- 爲了提高擴展性,Mybatis定義了緩存接口Cache,我們可以通過實現Cache接口來自定義二級緩存
1、一級緩存
一級緩存也叫做本地緩存:
- 與數據庫同一次會話期間查詢到的數據會放在本地緩存中。
- 以後如果需要獲取相同的數據,直接從緩存中拿,沒必要再去查詢數據庫。
測試代碼:
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
User user1 = mapper.selectUserById(1);
System.out.println(user1);
System.out.println("user==user1 ? "+ (user == user1));
sqlSession.close();
}
結果如下,可以看出第二次查詢ID爲1的用戶是直接得到結果的,並沒有從數據庫中查詢,而且兩次查出來的用戶是同一個用戶。
Opening JDBC Connection
Created connection 811760110.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@306279ee]
==> Preparing: select * from test.user where id=?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 王二, 123456
<== Total: 1
User{id=1, name='王二', pwd='123456'}
User{id=1, name='王二', pwd='123456'} // 這裏是第二次查詢ID爲1的用戶
user==user1 ? true // 兩次查出來的用戶是同一個用戶
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@306279ee]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@306279ee]
Returned connection 811760110 to pool.
2、二級緩存
- 由於一級緩存作用域太低了,所以誕生了二級緩存。
- 二級緩存也叫全局緩存,這裏的【全局】指的是命名空間,所以二級緩存是基於NameSpace級別的緩存。
- 二級緩存的工作機制:
- 轉存的概念:一個會話查詢的數據保存在當前會話的一級緩存中,如果當前會話關閉了,這個會話對應的一級緩存就沒了,但是查詢的數據會被轉存到二級緩存中。
- 新的會話查詢信息,就可以從二級緩存中獲取數據。
- 不同的Mapper查出的數據會放在自己對應的二級緩存中。
使用二級緩存的步驟:
第一步:在mybatis核心配置文件中顯式開啓全局緩存:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
注:"cacheEnabled"的默認值就是true,但是在這裏再顯示地寫出來,表明開啓全局緩存。
第二步:在要使用二級緩存的Mapper中開啓緩存:
<mapper namespace="com.xlq.dao.UserMapper">
<cache/>
</mapper>
還可以自定義緩存參數:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
意思是創建了一個 FIFO 緩存,每隔 60 秒刷新,最多可以存儲結果對象或列表的 512 個引用,而且返回的對象被認爲是隻讀的。
第三步:測試
先開啓一個SqlSession進行查詢ID爲1的用戶,然後關閉連接;再開啓另外一個SqlSession,查詢同一個用戶,結果表明:第二次查詢是直接在緩存中讀取,並沒有到數據庫中查詢。
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
sqlSession.close();
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.selectUserById(1);
System.out.println(user1);
System.out.println("user==user1 ? "+ (user == user1));
sqlSession1.close();
}
日誌打印結果:
Opening JDBC Connection
Created connection 1434041222.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5579bb86]
==> Preparing: select * from test.user where id=?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 王二, 123456
<== Total: 1
User{id=1, name='王二', pwd='123456'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5579bb86]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5579bb86]
Returned connection 1434041222 to pool.
Cache Hit Ratio [com.xlq.dao.UserMapper]: 0.5
User{id=1, name='王二', pwd='123456'}
user==user1 ? true
可能會出現的問題:如果在Mapper.xml中直接設置緩存<cache/>
,會報錯:
Cause: java.io.NotSerializableException: com.xlq.pojo.User
原因是實體類沒有實現序列化接口。因爲要把這個對象給緩存到內存中,就必須要能夠保存這個對象,就要實現序列化接口。
3、自定義緩存(瞭解即可)
Ehcache:是一種廣泛使用的開源Java分佈式緩存,主要面向通用緩存。
自定義緩存:自定義緩存類必須實現 org.apache.ibatis.cache.Cache 接口,且提供一個接受 String 參數作爲 id 的構造器。(與數據庫底層相關,較爲複雜)
目前主流是Redis作爲緩存數據庫。
緩存的原理
緩存順序:
- 先看二級緩存中有沒有數據
- 再看一級緩存中有沒有數據
- 都沒有,再查詢數據庫