MyBatis的簡介與使用

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&amp;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&amp;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><!--        &lt;!&ndash;配置單個類的別名&ndash;&gt;--><!--        <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&amp;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 進行配置。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/41991.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/41991.shtml
英文地址,請注明出處:http://en.pswp.cn/web/41991.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

imx6ull/linux應用編程學習(14) MQTT基礎知識

什么是mqtt&#xff1f; 與HTTP 協議一樣&#xff0c; MQTT 協議也是應用層協議&#xff0c;工作在 TCP/IP 四層模型中的最上層&#xff08;應用層&#xff09;&#xff0c;構建于 TCP/IP協議上。 MQTT 最大優點在于&#xff0c;可以以極少的代碼和有限的帶寬&#xff0c;為連接…

網絡資源模板--Android Studio 外賣點餐App

目錄 一、項目演示 二、項目測試環境 三、項目詳情 四、完整的項目源碼 原創外賣點餐&#xff1a;基于Android studio 實現外賣(點)訂餐系統 非原創奶茶點餐&#xff1a;網絡資源模板--基于 Android Studio 實現的奶茶點餐App報告 一、項目演示 網絡資源模板--基于Android …

在AvaotaA1全志T527開發板上使用AvaotaOS 部署 Docker 服務

Docker 是一個開源的應用容器引擎&#xff0c;讓開發者可以打包他們的應用以及依賴包到一個可移植的鏡像中&#xff0c;然后發布到任何流行的 Linux或Windows操作系統的機器上&#xff0c;也可以實現虛擬化。容器是完全使用沙箱機制&#xff0c;相互之間不會有任何接口。 準備…

dolphinscheduler-springboot集成

springboot集成dolphinscheduler 說明 為了避免對DolphinScheduler產生過度依賴&#xff0c;實踐中通常不會全面采用其內置的所有任務節點類型。相反&#xff0c;會選擇性地利用DolphinScheduler的HTTP任務節點功能&#xff0c;以此作為工作流執行管理的橋梁&#xff0c;對接…

信息技術課上的紀律秘訣:營造有序學習環境

信息技術課是學生們探索數字世界的樂園&#xff0c;但同時也是課堂紀律管理的挑戰場。電腦、網絡、游戲等元素可能分散學生的注意力&#xff0c;影響學習效果。本文將分享一些有效的策略&#xff0c;幫助教師在信息技術課上維持課堂紀律&#xff0c;確保教學活動順利進行。 制…

幾何建模基礎-樣條曲線和樣條曲面介紹

1.概念介紹 1.1 樣條曲線的來源 樣條的英語單詞spline來源于可變形的樣條工具&#xff0c;那是一種在造船和工程制圖時用來畫出光滑形狀的工具&#xff1a;富有彈性的均勻細木條/金屬條/有機玻璃條&#xff0c;它圍繞著按指定位置放置的重物或者壓鐵做彈性彎曲&#xff0c;以…

JS實現一個簡單的模糊匹配

1、示例數據如下&#xff1a; // 示例數據 const data [ { name: ‘Alice’, age: 25 }, { name: ‘Bob’, age: 30 }, { name: ‘Charlie’, age: 35 }, { name: ‘David’, age: 40 }, { name: ‘Eve’, age: 45 } ]; 2、模糊匹配函數 // 模糊匹配函數 function fuzzyMatch(…

基于LangChain的RAG開發教程(二)

v1.0官方文檔&#xff1a;https://python.langchain.com/v0.1/docs/get_started/introduction/ 最新文檔&#xff1a;https://python.langchain.com/v0.2/docs/introduction/ LangChain是一個能夠利用大語言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;能…

植物大戰僵尸融合嫁接版 MAC 版本下載安裝詳細教程

繼植物大戰僵尸雜交版火了之后&#xff0c;PVZ改版可謂是百花齊放&#xff0c;最近又有一個非常好玩的模式被開發出來了&#xff0c;他們稱為《植物大戰僵尸融合嫁接版》 該版本并沒有對植物卡牌做改動&#xff0c;而是可以將任意兩種植物疊放到一起進行融合&#xff0c;產生新…

思路打開!騰訊造了10億個角色,驅動數據合成!7B模型效果打爆了

世界由形形色色的角色構成&#xff0c;每個角色都擁有獨特的知識、經驗、興趣、個性和職業&#xff0c;他們共同制造了豐富多元的知識與文化。 所謂術業有專攻&#xff0c;比如AI科學家專注于構建LLMs,醫務工作者們共建龐大的醫學知識庫&#xff0c;數學家們則偏愛數學公式與定…

lvgl 本地化

生成語言包文件&#xff1a; lv_i18n compile -t en-GB.yml -o ui 正則匹配中文 "[\u4e00-\u9fa5]" _("[\u4e00-\u9fa5]") https://www.cnblogs.com/jerryqi/p/9604828.html 查找多個漢字體的 ("[\u4e00-\u9fa5]"[)]) _($1) "科室:"…

數據分析與挖掘實戰案例-電商產品評論數據情感分析

數據分析與挖掘實戰案例-電商產品評論數據情感分析 文章目錄 數據分析與挖掘實戰案例-電商產品評論數據情感分析1. 背景與挖掘目標2. 分析方法與過程2.1 評論預處理1. 評論去重2. 數據清洗 2.2 評論分詞1. 分詞、詞性標注、去除停用詞2. 提取含名詞的評論3. 繪制詞云查看分詞效…

昇思25天學習打卡營第12天 | LLM原理和實踐:MindNLP ChatGLM-6B StreamChat

1. MindNLP ChatGLM-6B StreamChat 本案例基于MindNLP和ChatGLM-6B實現一個聊天應用。 ChatGLM-6B應該是國內第一個發布的可以在消費級顯卡上進行推理部署的國產開源大模型&#xff0c;2023年3月就發布了。我在23年6月份的時候就在自己的筆記本電腦上部署測試過&#xff0c;當…

UI自動化測試框架:PO 模式+數據驅動(超詳細)

1. PO 設計模式簡介 什么是 PO 模式&#xff1f; PO&#xff08;PageObject&#xff09;設計模式將某個頁面的所有元素對象定位和對元素對象的操作封裝成一個 Page 類&#xff0c;并以頁面為單位來寫測試用例&#xff0c;實現頁面對象和測試用例的分離。 PO 模式的設計思想與…

Python學習中進行條件判斷(if, else, elif)

條件判斷是編程中必不可少的一部分&#xff0c;它讓程序可以根據不同的條件執行不同的代碼塊。在Python中&#xff0c;主要使用if、elif和else語句來實現條件判斷。 基本語法 在Python中&#xff0c;條件判斷的基本語法如下&#xff1a; if condition:# 當condition為True時…

一篇讀懂128陷阱

128陷阱 128陷阱的概念包裝器類自動裝箱自動拆箱128陷阱 Intager源碼equals 128陷阱的概念 首先想要清楚什么是128陷阱&#xff0c;需要了解一些概念 包裝器類 包裝器類&#xff08;Wrapper classes&#xff09;是Java中的一組類&#xff0c;它們允許將基本數據類型&#xf…

NCCL 中的一些輔助debug 知識點

1&#xff0c;調試nccl 啟動kernel的方法 ncclLaunchKernel cuLaunchKernelEx ncclStrongStreamLaunchKernel cudaLaunchKernel ncclLaunchOneRank cudaLaunchKernel 在 nccl lib 中&#xff0c;不存在使用<<<grid, block,,>>> 這種類似方式啟…

算法題型歸類整理及同類題型解法思路總結(持續更新)

1、最優路線 通用思路 1、遞歸 #案例1-最優路測路線 題目描述 評估一個網絡的信號質量&#xff0c;其中一個做法是將網絡劃分為柵格&#xff0c;然后對每個柵格的信號質量計算。 路測的時候&#xff0c;希望選擇一條信號最好的路線&#xff08;彼此相連的柵格集合&#x…

12種增強Python代碼的函數式編程技術

前言 什么是函數式編程&#xff1f; 一句話總結&#xff1a;函數式編程(functional programming)是一種編程范式&#xff0c;之外還有面向對象&#xff08;OOP&#xff09;、面向過程、邏輯式編程等。 函數式編程是一種高度抽象的編程范式&#xff0c;它倡導使用純函數&#x…

算法·二分

二分枚舉 適用條件&#xff1a; 答案有明顯上下界答案具有單調性:a滿足,若b>a可以知b必定滿足。本質上是枚舉的對數優化 思維技巧 解決問題->>驗證答案,明顯前者比后者更加困難若題目有最大值最小&#xff0c;最小值最大這種經典條件&#xff0c;隱含著答案有界 …