Mybatis
JDBC操作數據庫的缺點
- 存在大量的冗余代碼。
- 手工創建 Connection、Statement 等,效率低下。
- 手工將結果集封裝成實體對象。
- 查詢效率低,沒有對數據訪問進行優化。
Mybatis框架
簡介
MyBatis 本是 apache 的一個開源項目 iBatis, 2010年這個項目由 apache software foundation 遷移到了google code,并且改名為MyBatis 。2013年11月遷移到Github。
iBatis 一詞來源于 “internet” 和 “abatis” 的組合,是一個基于Java的持久層框架。iBatis 提供的持久層框架包括 SQL Maps 和 Data Access Objects(DAOs)
MyBatis 是一款優秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 POJO(Plain Ordinary Java Objects,普通 Java 對象)為數據庫中的記錄。
Mybatis獲取
官網:https://mybatis.org/mybatis-3/
Maven配置:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency>
使用Mybatis
工程搭建
引入依賴庫:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.17</version> </dependency>
config配置文件
在resources目錄下創建config.xml
1.配置JDBC環境;
2.注冊Mapper。
<?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"> <!--MyBatis配置--> <!--常用配置標簽的先后順序properties,settings,typeAliases,typeHandlers,plugins,environments,mappers如果配置文件中同時存在這些配置標簽,它們之間的順序必須按照上述列表排列--> <configuration><!--JDBC環境配置,選中默認環境--><environments default="dev"><!--Mysql數據庫環境配置--><environment id="dev"><!--事務管理,這里的JDBC是一個類的別名:org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory--><transactionManager type="JDBC"/><!--連接池,這里的POOLED也是一個類的別名:org.apache.ibatis.datasource.pooled.PooledDataSourceFactory--><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai&tinyInt1isBit=false"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!--Mapper注冊--><mappers><!--注冊Mapper文件的所在位置--></mappers> </configuration>
創建userMapper接口以及接口的映射文件
userMapper:
public interface UserMapper {User getUserByUsername(String username); }
userMapper.xml:
<!--userMapper.xml--> <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace = 所需實現的接口全限定名--> <mapper namespace="com.qf.mybatis.mapper.UserMapper"><!--id表示接口中的方法名,resultType表示查詢結果每一行數據對應的轉換類型--><select id="getUserByUsername" resultType="com.qf.mybatis.pojo.User"><!--#{arg0}表示獲取方法參數列表中的第一個參數值--><!--#{param1}表示獲取方法參數列表中的第一個參數值-->SELECT username,password,name,sex FROM user where username=#{arg0}</select> </mapper>
注冊Mapper接口
<mappers><!--注冊Mapper文件的所在位置--><mapper resource="mapper/userMapper.xml"/></mappers>
測試
構建SqlSessionFactory的構建者,獲取配置文件信息,根據配置文件信息構建SqlSessionFactory工廠,工廠開啟sqlsession會話。
以上是程序性操作
然后從會話中獲得接口的代理對象,底層是動態代理。
@Test public void getUserByUserNameTest() throws IOException {//構建SqlSessionFactory的構建者SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//獲取配置文件信息InputStream is = Resources.getResourceAsStream("config.xml");//根據配置信息構建工廠SqlSessionFactory factory = builder.build(is);//工廠開啟sql會話SqlSession session = factory.openSession();//從會話中獲得userMapper接口的代理對象(原理是動態代理)UserMapper userMapper = session.getMapper(UserMapper.class);//調用方法User user = userMapper.getUserByUsername("zs");System.out.println(user); }
properties文件配置
Mybatis支持properties文件的引入,這樣做的目的就是為了區分配置:不同的文件中描述不同的配置,這樣方便管理。 在 resources 目錄下新建 jdbc.properties 文件:
#jdbc.properties jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/lesson?characterEncoding=utf8&tinyInt1isBit=false jdbc.username=root jdbc.password=root
,然后在 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"> <!--MyBatis配置--> <configuration><!--引入jdbc.properties文件--><properties resource="jdbc.properties"/><!--JDBC環境配置,選中默認環境--><environments default="dev"><!--Mysql數據庫環境配置--><environment id="dev"><!--事務管理,這里的JDBC是一個類的別名:org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory--><transactionManager type="JDBC"/><!--連接池,這里的POOLED也是一個類的別名:org.apache.ibatis.datasource.pooled.PooledDataSourceFactory--><dataSource type="POOLED"> <!-- <property name="driver" value="com.mysql.cj.jdbc.Driver"/>--> <!-- <property name="url" value="jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai&tinyInt1isBit=false"/>--> <!-- <property name="username" value="root"/>--> <!-- <property name="password" value="123456"/>--><property name="diver" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><!--Mapper注冊--><mappers><!--注冊Mapper文件的所在位置--><mapper resource="mapper/userMapper.xml"/></mappers> </configuration>
類型別名
在Mapper接口映射文件userMapper.xml文件中
<!--id表示接口中的方法名,resultType表示查詢結果每一行數據對應的轉換類型--><select id="getUserByUsername" resultType="com.qf.mybatis.pojo.User">
resultType屬性配置很繁瑣,當方法很多的時候,開發效率大大降低,因此Mybatis提供了為類型定義別名的功能。該功能需要在config.xml中配置
<!--配置類型的別名:typeAlias方式和package方式只能選擇其一--> <typeAliases><!-- <!–配置單個類的別名–>--><!-- <typeAlias type="com.qf.mybatis.pojo.User" alias="user" />--><!--配置需要取別名的類的包,該包中所有類的別名均為類名--><package name="com.qf.mybatis.pojo"/> </typeAliases>
userMapper.xml:
<select id="getUserByUsername" resultType="User">
日志配置
Mybatis本身有提供日志功能,開啟日志需要在
config.xml
進行配置<!-- 打印SQL語句 STDOUT_LOGGING是一個類的別名:org.apache.ibatis.logging.stdout.StdOutImpl--><setting name="logImpl" value="STDOUT_LOGGING"/>
注:常用配置標簽的先后順序
properties,
settings,
typeAliases,
typeHandlers,
plugins,
environments,
mappers
如果配置文件中同時存在這些配置標簽,它們之間的順序必須按照上述列表排列。
Mybatis增刪改查
由于每次實現方法都需要構建SqlSessionFactory的構建者,獲取配置文件信息,根據配置文件信息構建SqlSessionFactory工廠,工廠開啟sqlsession會話。
因此把這部分封裝起來作為工具類:
FactoryUtil:
public class FactoryUtil {private static SqlSessionFactory factory;static {//構建SqlSessionFactory的構建者SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//獲取配置文件信息InputStream is = null;try {is = Resources.getResourceAsStream("config.xml");//根據配置信息構建工廠factory = builder.build(is);} catch (IOException e) {throw new RuntimeException(e);}}public static SqlSession getSqlSession() {return factory.openSession();} }
標簽
<select id="getUser"/> <insert id="addUser"/> <delete id="deleteUser"/> <update id="updateUser"/>
參數取值
在Mybatis中,參數取值有兩種方式:一種是#{表達式}, 另一種是 **${表達式} ** ;
#{表達式} 采用的是JDBC中的預編譯來實現,因此可以防止SQL注入。
**${表達式} ** 采用的是字符串拼接,因此常用在排序字段變化、分組字段變化、查詢表名變化等場景。
常用數據類型作為參數:
使用arg參數下標或者param參數位置獲取參數
如:
User getUserByUsername(String username);
<select id="getUserByUsername" resultType="User">SELECT username,password,name,sex FROM user where username=#{arg0} </select>
public void getUserByUserNameTest() throws IOException {SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);User user = userMapper.getUserByUsername("zs");System.out.println(user); }
實體對象作為參數
使用#{屬性名}獲取對象屬性參數
單個對象:
接口方法:
int addUser(User user);
xml映射:
<insert id="addUser">INSERT into user values (#{username},#{password},#{name},#{sex}) </insert>
public void addUser(){SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);User user = new User();user.setName("吉吉");user.setSex(1);user.setUsername("jj");user.setPassword("123456");int i = userMapper.addUser(user);try {session.commit();//不提交事務就不會對數據庫中的數據進行修改} catch (Exception e) {session.rollback();//如果提交失敗回滾事務}System.out.println(i); }
多個對象:
int updateUserPassword(User user1,User user2);
<update id="updateUserPassword">update user set password=#{arg0.password} where username=#{arg1.username} </update>
public void updateUser(){SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);User user1 = new User();User user2 = new User();user1.setUsername("zs");user2.setUsername("ls");user1.setPassword("123456");int i = userMapper.updateUserPassword(user1, user2);try {session.commit();} catch (Exception e) {session.rollback();}System.out.println(i); }
Map作為參數:
由于Map中存放的數據是通過鍵值對實現的,因此可以將Map當做一個實體類對象來看待。Map中的鍵就相當于實體類中的屬性名,Map中的值就相當于實體類中的屬性值。因此,其取值方式與實體類對象作為參數一樣。
int deleteUser(Map<String,Object> params);
<delete id="deleteUser">DELETE from user where username=#{username} and password=#{password} </delete>
public void deleteUser(){SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);Map<String, Object> params = new HashMap<>();params.put("username","jj");params.put("password","123456");int i = userMapper.deleteUser(params);try {session.commit();} catch (Exception e) {session.rollback();}System.out.println(i); }
參數注解
為了方便開發,Mybatis對參數提供了注解,從而可以給參數指定名稱,方便在對應的Mapper映射文件中使用
List<User> retrieveUsers(@Param("condition")Map<String,Object> params);
<select id="retrieveUsers" resultType="User">select * from user where password=#{condition.password} and sex=#{condition.sex}</select>
public void retriveUsers(){SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);Map<String, Object> params = new HashMap<>();params.put("password","321321");params.put("sex",1);List<User> users = userMapper.retrieveUsers(params);try {session.commit();} catch (Exception e) {session.rollback();}users.forEach(System.out::println);}
主鍵回填
當保存一條數據時,我們需要該數據的ID,ID生成有兩種方式:一種是數據庫自動生成,一種是程序通過編碼生成。Mybatis也提供了這兩種方式來生成ID,ID生成后可以設置到給定的屬性上,這個過程稱之為主鍵回填。
一般采用數據庫自動生成的ID,而不是程序編碼生成的,因為程序生成的意義不大,無法從數據庫中查詢。
創建表:
-- 創建表 DROP TABLE IF EXISTS score; CREATE TABLE score (-- 主鍵自增id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主鍵',name varchar(20) NOT NULL COMMENT '姓名',score double(5,2) DEFAULT NULL COMMENT '成績' ) ENGINE=InnoDB CHARSET=UTF8;
對應實體類:
@Data public class Score {private long id;private String name;private Double score;}
映射文件:
<?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.qf.mybatis.mapper.ScoreMapper"><insert id="addScore"><!-- selectKey表示選擇鍵 通常都是用于主鍵回填功能 keyProperty表示回填的值設置到哪個屬性上 resultType表示回填的值的數據類型 order表示主鍵回填的時機 AFTER表示數據保存后 BEFORE表示數據插入之前--><selectKey keyProperty="score.id" resultType="long" order="AFTER">SELECT LAST_INSERT_ID()</selectKey>INSERT INTO score(name,score)VALUES(#{score.name},#{score.score})</insert> </mapper>
注冊;
<mapper resource="mapper/scoreMapper.xml"/>
測試:
public void addScore(){SqlSession session = FactoryUtil.getSqlSession();ScoreMapper mapper = session.getMapper(ScoreMapper.class);Score score = new Score();score.setName("zs");score.setScore(99.0);int i = mapper.addScore(score);try {session.commit();} catch (Exception e) {session.rollback();}System.out.println(i); }
關鍵點:
使用
SELECT LAST_INSERT_ID()
獲取自動生成的主鍵值,并將其回填到score
對象的id
屬性中
結果映射
在SQL查詢時,我們經常會遇到數據庫表中設計的字段名與對應的實體類中的屬性名不匹配的情況,針對這種情況,Mybatis 提供了結果集映射,供用戶自己實現數據庫表中字段與實體類中屬性進行匹配。
DROP TABLE IF EXISTS employee;
CREATE TABLE employee(id int NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '員工編號',name varchar(30) NOT NULL COMMENT '姓名',entry_time datetime NOT NULL COMMENT '入職時間',leave_time datetime DEFAULT NULL COMMENT '離職時間'
) ENGINE=InnoDB CHARSET=UTF8;
// 創建實體類 員工
public class Employee {private long id;private String name;private Date entryTime;private Date leaveTime;//省略getter和setter//構造方法:要么無參,要么全參
}// 創建Mapper接口
public interface EmployeeMapper {List<Employee> getAllEmployees();
}
<?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.qf.mybatis.mapper.EmployeeMapper"><resultMap id="empMap" type="com.qf.mybatis.model.Employee"><id property="id" column="id" /><result property="name" column="name" /><!--數據表中列名與實體類中的屬性名匹配--><result property="entryTime" column="entry_time" /><!--數據表中列名與實體類中的屬性名匹配--><result property="leaveTime" column="leave_time" /></resultMap><select id="getAllEmployees" resultMap="empMap">SELECT id,name,entry_time,leave_time FROM employee</select>
</mapper>
也可以直接對表中字段重命名。
Mybatis級聯查詢
1. 一對一級聯查詢
創建簽證表和乘客表,其中一個乘客有多個簽證,而一個簽證只對應一個乘客:
DROP TABLE IF EXISTS passenger;
CREATE TABLE passenger (id bigint NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '乘客編號',name varchar(50) NOT NULL COMMENT '姓名',sex tinyint(1) NOT NULL DEFAULT '0' COMMENT '性別',birthday date NOT NULL COMMENT '生日'
) ENGINE=InnoDB CHARSET=UTF8;DROP TABLE IF EXISTS passport;
CREATE TABLE passport (id bigint NOT NULL AUTO_INCREMENT COMMENT '護照編號',office varchar(50) NOT NULL COMMENT '簽證機關',valid_time tinyint NOT NULL COMMENT '有效期限',nationality varchar(50) NOT NULL COMMENT '國籍',passenger_id bigint NOT NULL COMMENT '乘客編號',PRIMARY KEY (id),FOREIGN KEY (passenger_id) REFERENCES passenger (id)
) ENGINE=InnoDB CHARSET=UTF8;
創建對應的實體類,屬性名采用駝峰命名法:
@Data
public class Passenger {private long id;private String name;private int sex;private Date birthday;}//----------------------------------------------------
@Data
public class Passport {private long id;private String nationality;private int validTime;private String office;private Passenger passenger;
}
現在要通過查詢簽證表的同時查詢出乘客表
因此要寫是PassportMapper接口,其中的方法為獲取所有簽證對象
public interface PassportMapper {List<Passport> getAllPassports(); }
然后寫映射文件,因為是通過查詢簽證表查到乘客表的一對一級聯,所以只用寫簽證表的映射文件。
方式一:
簽證表的passenger字段和乘客表的id字段由一個參數passengerId進行連接,先查passport表,然后將參數作為索引查passenger表.
因此要寫PassengerMapper接口,其中的方法為獲取所有乘客對象。
public interface PassengerMapper {List<Passenger> getPassengers(); }
映射文件:
<?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.qf.mybatis.mapper.PassportMapper"><resultMap id="passportMap" type="Passport"><!--查詢單張表可以只寫實體類屬性和數據庫字段名不同的部分--><result property="validTime" column="valid_time"/><!--一對一的級聯查詢使用的是association標簽--><!--級聯查詢也支持傳遞參數,傳遞參數需要通過column屬性來傳遞,定義參數的語法:{參數名=列名,...,參數名n=列名n}--><association property="passenger" column="{passengerId = passenger_id}" select="getPassengers"/></resultMap><select id="getAllPassports" resultMap="passportMap">select * from passport</select><select id="getPassengers" resultType="Passenger">select * from passenger where id=#{passengerId}</select> </mapper>
方式二:
將兩個表連接起來,查詢連接后的表:
注意:在查詢復雜關系的表的時候需要在結果映射中將所有屬性和數據字段名都寫出來
<mapper namespace="com.qf.mybatis.mapper.PassportMapper"><resultMap id="passportMap" type="Passport"><id column="id" property="id" /><result column="nationality" property="nationality" /><result column="office" property="office" /><result column="valid_time" property="validTime" /><association property="passenger" javaType="passenger"><id column="passengerId" property="id" /><result column="name" property="name" /><result column="sex" property="sex" /><result column="birthday" property="birthday" /></association></resultMap><select id="getAllPassports" resultMap="passportMap">select a.id,a.nationality,a.office,a.valid_time,b.id passengerId,b.name,b.sex,b.birthday from passport a inner join passenger b on a.passenger_id = b.id</select> </mapper>
2. 一對多級聯查詢
association改為collection
創建班級表和學生表:
DROP TABLE IF EXISTS class;
CREATE TABLE class (id int NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '班級編號',name varchar(50) NOT NULL COMMENT '名稱'
) ENGINE=InnoDB CHARSET=UTF8;DROP TABLE IF EXISTS student;
CREATE TABLE student (id bigint NOT NULL AUTO_INCREMENT COMMENT '學號',name varchar(50) NOT NULL COMMENT '姓名',class_id int NOT NULL COMMENT '班級編號',PRIMARY KEY (id),FOREIGN KEY (class_id) REFERENCES class (id)
) ENGINE=InnoDB CHARSET=UTF8;
public class Student {private long id;private String name;
}
public class Clazz {private int id;private String name;private List<Student> students; //集合作為屬性
}public interface ClazzMapper {List<Clazz> getClazzList();
}
方式一:查詢兩次
因此還需要StudentMapper接口:
public interface StudentMapper {List<Student> getStudents(); }
映射文件:
<mapper namespace="com.qf.mybatis.mapper.ClazzMapper"><resultMap id="clazzMap" type="Clazz"><id property="id" column="id"/><result property="name" column="name"/><!--一對多 級聯 方式--><collection property="students" select="getStudents" column="{sid=id}"/></resultMap><select id="getClazzList" resultMap="clazzMap">select * from class</select><select id="getStudents" resultType="Student">select * from student where class_id = #{sid}</select></mapper>
方式二:
查詢兩個表的連接表:
<mapper namespace="com.qf.mybatis.mapper.ClazzMapper"><resultMap id="clazzMap" type="Clazz"><id column="id" property="id"/><result column="name" property="name"/><collection property="students" ofType="Student"><id column="sid" property="id"/><result column="sname" property="name"/></collection></resultMap><select id="getClazzList" resultMap="clazzMap">selecta.id,a.name,b.id sid,b.name snamefrom class a inner join student bon a.id=b.class_id</select> </mapper>
注意:重復的名字需要重命名。
3.RBAC權限模型查詢
RBAC權限模型介紹
RBAC(Role Based Access Control,基于角色的訪問控制),就是用戶通過角色與權限進行關聯,而不是直接將權限賦予用戶。
如現在有以下表:
數據表: 用戶表 username varchar(50) primary key password varchar(200) name varchar(50)角色表 id int(11) primary key auto_increment name varchar(50)用戶角色表 username varchar(50) role_id int(11)菜單表 id int(11) primary key auto_increment name varchar(50) parent_id int(11)角色菜單表 role_id int(11) menu_id int(11)
關系如下:
RBAC模型中,用戶與角色之間、角色與權限之間,一般是多對多的關系。
現在有一個需求,根據用戶查詢到對應的菜單。
這里采用非級聯查詢和級聯查詢(均采用查詢一次的方式)
實體類:
@Data public class User {private String username;private String password;private String name;private List<Menu> menus; } //---------------------------------- @Data public class Menu {private int id;private String name; }
非級聯查詢
接口方法:
List<Menu> getMenus(String username);
映射文件:
<resultMap id="menuMap" type="Menu"><id column="mid" property="id"/><result column="mname" property="name"/> </resultMap> <select id="getMenus" resultMap="menuMap">select m.id mid,m.name mname from menu m join role_menu rm on m.id=rm.menu_idjoin roles r on rm.role_id=r.idjoin user_role ur on r.id=ur.role_idjoin user u on ur.username = u.usernamewhere u.username=#{username} </select>
測試:
@Test public void getMenusByUsername() throws IOException {SqlSession session = getSession();UserMapper mapper = session.getMapper(UserMapper.class);List<Menu> menus = mapper.getMenus("jj");try {session.commit();} catch (Exception e) {session.rollback();}menus.forEach(System.out::println); }private SqlSession getSession() throws IOException {SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();InputStream is = Resources.getResourceAsStream("config.xml");SqlSessionFactory factory = builder.build(is);SqlSession session = factory.openSession();return session; }
級聯查詢
接口方法:
List<User> getUsers();
映射文件:
<resultMap id="userMap" type="User"><id property="username" column="username"/><result property="password" column="password"/><result property="name" column="uname"/><result property="sex" column="sex"/><collection property="menus" ofType="Menu"><id property="id" column="id"/><result property="name" column="mname"/><result property="parentId" column="parent_id"/></collection> </resultMap><select id="getUsers" resultMap="userMap">select u.username,u.password,u.name uname,m.id,m.name mnamefrom user uleft join user_role ur on u.username = ur.usernameleft join roles r on ur.role_id = r.idleft join role_menu rm on r.id = rm.role_idleft join menu m on rm.menu_id = m.parent_id </select>
測試:
@Test public void getMenusByUser() throws IOException {SqlSession session = getSession();UserMapper mapper = session.getMapper(UserMapper.class);List<User> users = mapper.getUsers();try {session.commit();} catch (Exception e) {session.rollback();}users.forEach(System.out::println); }private SqlSession getSession() throws IOException {SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();InputStream is = Resources.getResourceAsStream("config.xml");SqlSessionFactory factory = builder.build(is);SqlSession session = factory.openSession();return session; }
級聯查詢與非級聯查詢的區別
級聯查詢查到的結果是包含其他類的集合作為屬性的類,這里就是User,User中含有menu的集合屬性,因此sql查詢中查詢的目標含有user表外的其他表的字段內容,且這些表之間有連接關系。而非級聯查詢只能查詢當前表中的內容,返回的是查詢對象的類。
動態SQL
sql標簽
將特定的SQL代碼封裝起來,方便進行重用
<!--多條SQL都會使用的字段可以使用sql標簽來定義,使用時通過include標簽來引入--> <sql id="fields">username,password,name </sql> <select id="getUser" resultType="User">select <include refid="fields"/> from user where username=#{username} </select>
if標簽
滿足標簽的驗證內容時才將標簽內的內容拼接至sql語句中
<!--if標簽--> <!--直接在sql語句中插入--> <select id="getUserList" resultType="User">select * from user where 1=1<if test="conditions.name!=null and conditions.name!=''">and name like concat('%',#{conditions.name},'%')</if> </select>
where標簽
代替sql語句中的where,可以與if聯合使用。當 where 標簽內存在查詢條件時, where 標簽會在SQL代碼中添加 WHERE 關鍵字; 當 where 標簽內不不存在查詢條件時, where 標簽將忽略 WHERE 關鍵字的添加。除此之外,where 標簽還將自動忽略其后的 AND 或者 OR 關鍵字。
<select id="getUserList" resultType="User">select * from user<!--where標簽,會自動添加where并忽略后面的and或者or關鍵字--><where><if test="conditions.name!=null and conditions.name!=''">and name like concat('%',#{conditions.name},'%')</if></where> </select>
set標簽
代替sql語句中的update xxx set這里的set,實現動態更新。
set標簽會忽略最后一個sql子句的后綴,比如逗號。
<update id="updateUserPassword">update user<set><if test="conditions.password!=null and conditions.password!=''">password = #{conditions.password}</if><where><if test="conditions.username!=null and conditions.username!='' ">and username = #{conditions.username}</if></where></set> </update>
trim標簽
Mybatis 提供了 trim 標簽來代替 where 標簽和 set 標簽。
<!-- 其中 prefixOverrides 屬性表示要被重寫的前綴,prefix 屬性表示用來替換重寫的前綴內容。suffix和suffixOvverdides 屬性表示對后綴的處理--> <trim prefix="" prefixOverrides="" suffix="" suffixOverrides=""></trim>
<select id="getScores" resultType="score">SELECT id,name,score FROM score<trim prefix="WHERE" prefixOverrides="AND"><if test="params.name != null and params.name != ''">AND name LIKE CONCAT('%', #{params.name}, '%')</if><if test="params.scoreFrom != null and params.scoreFrom != ''">AND score >= #{params.scoreFrom}</if><if test="params.scoreTo != null and params.scoreTo != ''"><![CDATA[AND score <= #{params.scoreTo}]]></if></trim> </select><update id="updateScore">UPDATE score<trim suffixOverrides="," suffix=""><if test="s.name != null and s.name != ''">name = #{s.name},</if><if test="s.score != null and s.score != ''">score = #{s.score},</if></trim><where><if test="s.id != null and s.id != ''">AND id = #{s.id}</if></where> </update>
foreach標簽
collection表示遍歷的元素類型,如果參數沒有使用注解命名,那么該屬性值只能是list,array,map其中之一;如果參數使用了注解命名,那么該屬性值直接使用注解指定的名稱即可。
item表示每次遍歷時使用的對象名
open表示前面添加的內容
close表示最后添加的內容
seperator表示每次遍歷時內容組裝使用的分割符
index表示遍歷時的下標<foreach collection="" item="" open="" seperator="" close="" index=""></foreach>
例:
<delete id="deleteUserByUsername">delete from user where username in<foreach collection="usernames" item="username" open="(" separator="," close=")">#{username}</foreach> </delete>
Mybatis緩存
什么是緩存?
緩存是存儲在內存中的臨時數據,將用戶經常查詢的數據放在緩存(內存)中,用戶再次查詢數據的時候就不用從磁盤上(關系型數據庫數據文件)查詢,從緩存中查詢,能夠提高查詢效率,解決了高并發系統的性能問題。
為什么使用緩存?
減少和數據庫的交互次數,提高效率
緩存的對象
經常查詢并且很少改變的數據
一級緩存(沒用)
又名Session緩存,簡單地說,整個緩存的管理都由Session完成,開發者不需要做任何的事情,這個緩存本身就存在,但是這個一級緩存不能跨越Session,所以沒用。
public void getUserByUserNameTest() throws IOException {SqlSession session = FactoryUtil.getSqlSession();//從會話中獲得userMapper接口的代理對象(原理是動態代理)UserMapper userMapper = session.getMapper(UserMapper.class);//調用方法userMapper.getUserByUsername("zs");userMapper.getUserByUsername("zs");//清空session中的緩存session.clearCache();userMapper.getUserByUsername("zs");userMapper.getUserByUsername("zs");// System.out.println(user); }
在這個測試中,日志中只會打印出兩遍sql語句,第一遍是第一次調用方法進行查詢的時候,使用sql語句后session會利用一級緩存將查詢結果保存,因此再次查詢不會再次用sql去查。第二次是由于清空了session中的緩存,所以會重新去查詢。
二級緩存
能跨越session,可以使用mybatis默認的簡單的二級緩存( 一個簡單的、非持久化的內存緩存),也可以引入外部緩存庫。
使用外部緩存庫:
導入ehcache-core包和mybatis-ehcache包(這是個中間包,承上啟下,用于整合ehcache框架和mybatis.cache框架)。
<dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-core</artifactId><version>2.6.11</version></dependency><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.1</version></dependency>
創建ehcache.xml文件,不用記,只需要根據官方文檔改數據就行。這里需要改diskStore中數據在硬盤上的存儲位置。
<ehcache><diskStore path="java.io.tmpdir"/><cache name="com.example.MyMapper"maxEntriesLocalHeap="10000"eternal="false"timeToIdleSeconds="300"timeToLiveSeconds="600"overflowToDisk="true"diskPersistent="false"diskExpiryThreadIntervalSeconds="120"></cache> </ehcache>
然后再配置文件中的settings中需要開啟二級緩存
<sesstings name="cacheEnabled" value="true"/>
哪個Mapper.xml的查詢中需要使用二級緩存就在哪里進行配置
<cache type="org.mybatis.caches.EhcacheCache"></cache>
使用默認的二級緩存:
不用導入依賴,全局緩存相同:
<sesstings name="cacheEnabled" value="true"/>
在mapper.xml中:
<!-- cache標簽表示使用緩存flushInterval:表示緩存刷新時間,單位是毫秒readyOnly:表示是否只讀;true 只讀,MyBatis 認為所有從緩存中獲取數據的操作都是只讀操作,不會修改數據。MyBatis 為了加快獲取數據,直接就會將數據在緩存中的引用交給用戶。不安全,速度快。讀寫(默認):MyBatis 覺得數據可能會被修改size:表示存放多少條數據eviction: 緩存回收策略,有這幾種回收策略LRU - 最近最少回收,移除最長時間不被使用的對象FIFO - 先進先出,按照緩存進入的順序來移除它們SOFT - 軟引用,移除基于垃圾回收器狀態和軟引用規則的對象WEAK - 弱引用,更積極的移除基于垃圾收集器和弱引用規則的對象--> <cache flushInterval="300000" readOnly="true" size="10000" eviction="LRU"/>
測試:
@Test public void getUserByUserNameTest() throws IOException {SqlSession session = FactoryUtil.getSqlSession();UserMapper userMapper = session.getMapper(UserMapper.class);userMapper.getUserByUsername("zs");//需要提交并關閉才能進入二級緩存session.commit();session.close();//------------------------------------------------SqlSession session1 = FactoryUtil.getSqlSession();UserMapper userMapper1 = session1.getMapper(UserMapper.class);userMapper1.getUserByUsername("zs");session1.commit();session1.close();//這時日志中只有一次sql語句 }
注意: 二級緩存失效
二級緩存緩存數據的前提是查詢的 SqlSession 關閉,如果 SqlSession 沒有關閉,那么數據將不會進入二級緩存,再次進行同構查詢時,二級緩存由于沒有數據,查詢將進入數據庫,造成二級緩存失效的現象。
另一種情況是,當前查詢的 SqlSession 已經關閉,數據也進入了二級緩存,但在下一次查詢之前,如果中間發生了更新操作,該操作更新的數據在的二級緩存中存在,那么二級緩存也將失效。
分頁插件 PageHelper
Mybatis中的攔截器:
MyBatis的攔截器可以攔截Executor、ParameterHandler、ResultSetHandler和StatementHandler這四種類型的方法。
1.Executor:負責執行SQL語句,是MyBatis中最核心的組件之一。它負責管理緩存、執行SQL語句、處理緩存中的數據等。
2.ParameterHandler:負責處理SQL語句中的參數,將Java對象轉換為JDBC Statement所需的參數。
3.ResultSetHandler:負責處理SQL查詢結果集,將JDBC返回的ResultSet對象轉換為Java對象。
4.StatementHandler:負責處理SQL語句的生成和執行,包括SQL語句的預編譯、參數設置等操作。
這個插件本質上也是一個攔截器,要實現分頁,就可以攔截Executor中的Query方法,然后取出這個SQL語句,取出表名,通過表名構建統計的SQL語句 **select count(*) from 表名,**于是向數據庫發送一次請求,拿到數據的條目數 total,然后取出要查詢的頁碼和每一頁數據的個數,計算應該從哪里查詢,查詢多少條數據,于是構建第二個SQL語句,**原來的SQL語句 limit 查詢的開始位置,查詢的條目數,**得到查詢數據的結果,然后將total和數據封裝到一個類中進行數據的返回…
使用
導入分頁插件的包pagehelper
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.3.0</version> </dependency>
配置文件中進行分頁插件配置plugins
<!-- config.xml中進行配置 --> <plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>
測試
@Test public void getAllUsers(){SqlSession session = FactoryUtil.getSqlSession();UserMapper mapper = session.getMapper(UserMapper.class);PageHelper.startPage(2,3);//查詢第二頁,每頁三條數據,這句必須在查詢前!List<User> allUsers = mapper.getAllUsers();PageInfo<User> pageInfo = new PageInfo<>(allUsers);//將查詢結果保存到PageInfo對象中System.out.println("總條數:"+ pageInfo.getTotal());System.out.println("總頁數" + pageInfo.getPages());pageInfo.getList().forEach(System.out::println);//展示查詢結果try {session.commit();} catch (Exception e) {session.rollback();} finally {session.close();} }
注意:設置查詢頁碼和每頁條數的語句必須在調用查詢方法之前,否則查詢的結果將不會實現分頁效果。
配置數據源 Druid
Druid 是阿里巴巴開源平臺上的一個項目,是性能最好的數據庫連接池,如何在Mybatis中配置該數據源呢?
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version>
</dependency>
創建 DruidDataSourceFactory, 并繼承 PooledDataSourceFactory,并替換數據源
public class DruidDataSourceFactory extends PooledDataSourceFactory {public DruidDataSourceFactory() {this.dataSource = new DruidDataSource();//替換數據源}
}
<!--config.xml-->
<dataSource type="com.qf.mybatis.datasource.DruidSourceFactory"><!-- <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>--><!-- <property name="url" value="jdbc:mysql://localhost:3306/lesson?serverTimezone=Asia/Shanghai&tinyInt1isBit=false"/>--><!-- <property name="username" value="root"/>--><!-- <property name="password" value="123456"/>--><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource>
注意: 在 Druid 數據源中,屬性名稱是
driverClassName
,而不是driver
。因此,需要使用driverClassName
進行配置。