Mybatis框架
? ? ? ? Mybatis的含義:Mybatis框架是一個持久層框架,幾乎解決了jdbc代碼在手動設置參數和對結果集的手動獲取問題,原本是apache公司的開源項目,最后轉給Google公司。Mybatis會將參數封裝在一個對象中傳遞給數據庫,并將sql語句執行后的結果集封裝成對象。
? ? ? ? 它提供全局配置文件,建立與數據庫的連接;將接口進行分裝并提供類和方法實現對數據庫的鏈接和操作;對sql語句執行后的結果進行高級映射并封裝成對象;支持動態sql;支持緩存。
? ? ? ? Mybatis中存在自動映射,因為當在數據庫中查詢的表的屬性名和類中的屬性名完全相同時才會自動映射并封裝,所以需要保證類中的屬性和數據庫中的屬性名相同;如果類中的屬性為私有屬性,那么類中必須實現get和set方法;還需要保證類中要有無參構造方法,由于mybatis在數據庫中查詢數據后需要創建對象,調用對應類的無參構造方法,如果找不到對應的無參構造方法,mybatis就會報錯;當數據庫中的屬性名存在駝峰命名,mybatis也是會進行自動映射,但前提是需要開啟屬性mapUnderscoreToCamelCase,這個屬性需要在全局配置文件中設置,表示是否開啟駝峰命名的自動映射,true為開啟,開啟后例如在數據庫中的屬性名為student_id,那么類中的屬性只要設置為studentId這種駝峰命名的方式就可以進行自動映射。
搭建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="${driver}"/>
? ? ? ? <property name="url" value="${url}"/>
? ? ? ? <property name="username" value="${username}"/>
? ? ? ? <property name="password" value="${password}"/>
? ? ? </dataSource>
? ? </environment>
? </environments>
? <mappers>
? ? <mapper resource="org/mybatis/example/BlogMapper.xml"/>
? </mappers>
</configuration>
? ? ? ? 1、在項目的pom.xml配置文件中導入mybatis的jar包
? ? ? ? 2、配置全局配置文件(數據庫連接信息)
? ? ? ? 在resources目錄創建一個.xml文件用來配置全局文件,并在文件中寫入配置信息
? ? ? ? 其中<dataSource>標簽用來配置連接數據庫的配置信息,其中driver的值為com.mysql.cj.jdbc.Driver,url的值為jdbc:mysql://127.0.0.1:3306/mybatisdb?serverTimezone=Asia/Shanghai,username和password則為自己連接數據庫的用戶名和密碼,這里我使用的數據庫是SQLyog
? ? ? ? <dataSource type="POOLED">如果type為unpooled那么表示獲取連接時不是從連接池中獲取,而是返回一個新建的連接;如果type為pooled
? ? ? ? (1)?先先判斷空閑連接池內有沒有空閑連接,如果還有則給你返回?個空閑連接。
? ? ? ? (2)、如果沒有空閑連接,則去活動連接池內看看還有沒有位置,如果還有,則new?個連接給你返回
? ? ? ? (3)、如果活動連接池沒有位置了,則返回在活動連接池使?最久的連接。意思就是給你返回?個在活動連接池內待最久的連接
? ? ? ? <mappers>標簽用來添加映射配置文件,一個映射添加一個<mapper>標簽,標簽中resource屬性表示映射文件對于全局配置文件的相對地址
? ? ? ? 3、寫sql映射,訪問接口
? ? ? ? 在創建映射文件之前需要先創建和映射文件進行綁定的接口,一個映射文件對應一個接口,配個映射配置文件中都需要加上
<?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=""></mapper>
? ? ? ? <mapper>標簽中用來寫sql語句,namespace屬性的值對應和此映射文件所綁定的接口的包名,例如
? ? ? ? 這樣映射文件就和接口進行了綁定,我們還需要在配置文件中獲取連接數據庫的信息以及構建SqlSessionFactory,由于SqlSessionFactory一旦創建就會一直存在于Mybatis的應用過程中,并且由于創建SqlSessionFactory的開銷過大 ,所以我們在構建SqlSessionFactory時只需要創建一次即可,所以我們可以創建一個類并將創建SqlSessionFactory的方法放在這個類的靜態代碼塊中,這樣即使多次調用這個類但是創建SqlSessionFactory只會執行一次。
public class MyBatisUtil {
? ? static SqlSessionFactory sqlSessionFactory = null;
?
? ? static {
? ? ? ? try {
? ? ? ? ? ? // ? ? ? ?將全局配置文件放入到流中
? ? ? ? ? ? InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
? ? ? ? ? ? // ? ? ? ?與數據庫建立連接
? ? ? ? ? ? sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
? ? ? ? } catch (IOException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
?
? ? public SqlSession getSqlSession() {
? ? ? ? return sqlSessionFactory.openSession();//通過SqlSessionFactory對象獲取SqlSession
? ? }
}
? ? ? ? getSqlSession方法時用來通過SqlSessionFactory對象的openSession方法獲取SqlSession對象,我們可以通過SqlSession對象來執行與參數和返回值相匹配的接口
? ? ? ? 如果向執行sql語句,首先要在接口中定義抽象方法,并確定參數列表和返回值;然后在映射文件中寫對應的sql語句,如果為select語句則需要在<select>標簽中書寫sql語句,如果為insert語句則需要在<insert>標簽中書寫sql語句,update和delect語句同理。
public interface StudentDao {
? ? Student find(int id);//查詢通過id查詢學生的信息
}
? ? ? ? sql語句中的id需要與接口中的方法名相同,resultType為返回值類型這里的類型為Student,由于Student為自定義的類型,所以需要寫該類型的包名,#{id}表示傳過來的參數的值
<select id="find" resultType="com.ffyc.mybatisdemo.model.Student">
? ? ? ? select * from student where id = #{id}
</select>
? ? ? ? 以下為具體實現的方法
public void findStudent() {
? ? ? ? SqlSession sqlSession = new MyBatisUtil().getSqlSession();//通過類獲取SqlSession對象
? ? ? ? StudentDao studentDao = sqlSession.getMapper(StudentDao.class);//通過接口的class(類)對象,獲取代理對象,由于接口和映射配置文件綁定,所以可以通過代理對象調用接口中的方法
? ? ? ? Student student = studentDao.find(1);//通過代理對象調用接口中的find方法,并傳入參數1
? ? ? ? System.out.println(sqlSession);
? ? ? ? sqlSession.close();
? ? }
? ? ? ? 4、測試
? ? ? ? 執行方法后的結果如下,得到如下結果需要在Student中實現toString方法
參數傳遞問題
? ? ? ? 與映射器綁定的接口中的方法中的參數可以是任意一個,因為映射器配置文件中的id屬性的值和對應接口的方法名相同,所以同一個接口中的方法不能重名。當方法中的參數為一個時,直接將該參數進行傳遞即可;而當參數為多個時需要使用@Param注解對參數進行綁定,@Param里的值為類中的屬性名,與其綁定的則為形參。
void find(@Param("id")Integer id,@Param("no")Integer no,@Param("name")String name);
? ? ? ? 當接口中的參數過于多時,我們可以將參數封裝在一個對象中,通過傳遞對象來傳遞參數,但是我們還需要在映射器中加入parameterType屬性來說明傳遞的參數的類型。
void find(Student student);
<select id="find" parameterType="Student"></select>
增刪改查
? ? ? ? 當我們對數據庫進行增添操作時,我們先把數據封裝在對象中并將值傳給數據庫中進行操作,此時如果我們還想通過剛新插入的數據的id查詢其它的內容,由于id是自增的是由數據庫進行自加的,所以我們并不知道id是多少。這時我們可以通過在sql語句的<insert>標簽中添加三個屬性,就可以獲取到數據庫在對數據進行添加后的id,并將id封裝在對象的屬性中。useGeneratedKeys="true"表示是否開啟將自增屬性傳回,keyColumn="id"表示數據庫表中的自增屬性,keyProperty="id"表示要將獲取到的自增屬性的值賦值給類中的哪個屬性,這樣能獲取自增屬性id的條件是id得是自增屬性。
<insert id="saveAdmin" parameterType="Admin" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
? ? ? ? insert into admin(account,password,gender)value(#{account},#{password},#{gender})
</insert>
? ? ? ? 在新增,刪除和修改操作中,我們在將SqlSession關閉之前,需要將SqlSession的實例化對象的commit方法進行提交,而查詢操縱則不需要。因為查詢操作沒有對數據庫中的數據進行改變。
? ? ? ? Mybatis中還存在增刪改查操作的注解標簽,一般如果某些增刪改查操作的sql語句較為簡單,我們就可以直接在對應的接口上面通過注解標簽的形式來進行操作。
@Select("select * from grade where id = #{id}")
? ? int selectGrade(int id);
#{}和${}的區別
? ? ? ? #{}為占位符,通過預編譯的方式先用?代替出現占位符的地方,等到將sql語句編譯完成再將傳入的參數,可以防止sql注入
? ? ? ? ${}為拼接符,拼接符就像字符串一樣被Mybatis拼接到sql語句中,不能防止sql注入
結果處理
? ? ? ? 結果處理就是Mybatis對sql語句執行后的結果進行的封裝處理,有時我們不止只在一張表中進行查詢操作,返回的結果集中可能存在多個表中的數據,這時我們就需要在mapper映射器中將所有的映射關系進行配置,因為Mybatis在多表查詢后時不會將結果進行自動映射并封裝的。
? ? ? ? 例如在學生和年級關系中,如果想要查詢一個學生的信息和其所對應的年級信息,這是一個多對一關系的關聯查詢,查詢的結果集中存在不屬于學生表的屬性,這時我們需要在自定義的學生類中添加一個年級類型的屬性用來存放和年級相關的所有信息,并在mapper映射器中配置各個查詢結果的屬性所對應類中的屬性。
public ?class Student {
? ? private Integer id;
? ? private String name;
? ? private String gender;
? ? private Grade grade;
?
? ? public Integer getId() {
? ? ? ? return id;
? ? }
?
? ? public void setId(Integer id) {
? ? ? ? this.id = id;
? ? }
?
? ? public Integer getNo() {
? ? ? ? return no;
? ? }
?
? ? public void setNo(Integer no) {
? ? ? ? this.no = no;
? ? }
?
? ? public String getName() {
? ? ? ? return name;
? ? }
?
? ? public void setName(String name) {
? ? ? ? this.name = name;
? ? }
?
? ? public String getGender() {
? ? ? ? return gender;
? ? }
?
? ? public void setGender(String gender) {
? ? ? ? this.gender = gender;
? ? }
?
? ? public Grade getGrade() {
? ? ? ? return grade;
? ? }
?
? ? public void setGrade(Grade grade) {
? ? ? ? this.grade = grade;
? ? }
?
? ? @Override
? ? public String toString() {
? ? ? ? return "Student{" +
? ? ? ? ? ? ? ? "id=" + id +
? ? ? ? ? ? ? ? ", no=" + no +
? ? ? ? ? ? ? ? ", name='" + name + '\'' +
? ? ? ? ? ? ? ? ", gender='" + gender + '\'' +
? ? ? ? ? ? ? ? ", grade=" + grade +
? ? ? ? ? ? ? ? ", admin=" + admin +
? ? ? ? ? ? ? ? '}';
? ? }
}
public class Grade {
? ? private int id;
? ? private String name;
?
? ? 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;
? ? }
?
? ? @Override
? ? public String toString() {
? ? ? ? return "Grade{" +
? ? ? ? ? ? ? ? "id=" + id +
? ? ? ? ? ? ? ? ", name='" + name + '\'' +
? ? ? ? ? ? ? ? ", studentList=" + studentList +
? ? ? ? ? ? ? ? '}';
? ? }
}
? ? ? ? 如果查詢結果集中的屬性存在于多個表中,我們在查詢<select>標簽中的resultType屬性將會變為resultMap屬性,其值為<resultMap>標簽中自定義的id的值,type="Student"表示返回結果集的類型,<resultMap>標簽中有<id>和<result>標簽,<id>標簽代表數據庫的表中主鍵屬性,<result>標簽則代表其他屬性,column="id"表示在數據庫表中的屬性名,property="id"表示自定義類中的屬性名。如果類中存在其他自定義類型的屬性則使用<association>標簽表示,其中也存在<id>和<result>標簽配置方式和前面相同。我們還需要人為的在查詢語句中給結果的屬性列起別名,這樣可以保證在配置映射關系時一個結果集的屬性名可以對應一個類中的屬性,這是多對一關系結果集映射關系的配置。
<resultMap id="findInfo" type="Student">
? ? ? ? <id column="id" property="id"></id>
? ? ? ? <result column="name" property="name"></result>
? ? ? ? <result column="gender" property="gender"></result>
? ? ? ? <association property="grade" javaType="Grade">
? ? ? ? ? ? <result column="gname" property="name"></result>
? ? ? ? </association>
</resultMap>
? ? <select id="findStudent" resultMap="findInfo">
? ? ? ? SELECT s.id,s.name,s.gender,g.name gname FROM student s LEFT JOIN grade g ON s.gradeid = g.id WHERE s.id = #{id}
? ? </select>
? ? ? ? 如果我們要查詢在一個年級中有多少個學生以及每個學生的基本信息,這是一個一對多關系的關聯查詢,這時我們也需要在年級類中添加新的屬性,由于是一對多關系所以一個年級就會對應多個學生,所以我們需要添加一個存放學生信息的集合屬性。
public class Grade {
? ? private int id;
? ? private String name;
? ? private List<Student> studentList;
}
? ? ? ? 配置映射關系的方式和前面大致相同,仍然是寫在一個<resultMap>標簽中,當屬性為集合時我們需要使用<collection>標簽,property="studentList"表示在類中這個集合的名字,javaType="list"表示這個集合的類型,ofType="Student"表示集合中的泛型是什么類型,其余的配置方式和前面的都是一樣的。
<resultMap id="findGrade" type="Grade">
? ? ? ? <id column="id" property="id"></id>
? ? ? ? <result column="name" property="name"></result>
? ? ? ? <collection property="studentList" javaType="list" ofType="Student">
? ? ? ? ? ? <id column="sid" property="id"></id>
? ? ? ? ? ? <result column="sname" property="name"></result>
? ? ? ? </collection>
</resultMap>
? ? <select id="findStudent" resultMap="findGrade">
? ? ? ? SELECT
? ? ? ? ? g.id,
? ? ? ? ? g.name,
? ? ? ? ? s.id sid,
? ? ? ? ? s.name sname
? ? ? ? FROM
? ? ? ? ? grade g
? ? ? ? ? LEFT JOIN student s
? ? ? ? ? ? ON g.id = s.gradeid
? ? </select>
? ? ? ? 在這種一對多的關聯查詢中我們還可以使用嵌套查詢來解決分頁問題的產生,通過將一個復雜的查詢轉換為多個簡單查詢。例如查詢所有年級以及每個年級對應的所有學生的信息。具體的思路是我們先通過一個簡單查詢將所有的年級信息查詢出來,再根據年級的id來查詢每個年級所對應的所有學生。
? ? ? ? 其中我們還是使用<resultMap>標簽進行對映射關系的配置,和前面的關聯查詢不同<collection>標簽的屬性要比前面多兩個column="id"表示我們接下來可能需要用到的屬性,這里我們是需要根據年級的id進行之后的查詢操作的,select="findStudent2"表示一個自定義的名字,和另一個簡單查詢的id值相同并且另一個簡單查詢的返回值類型也就變為了Student類型,還是同樣將需要的學生信息的屬性配置到<collection>標簽中。
<resultMap id="GradeMap" type="Grade">
? ? ? ? <id column="id" property="id"></id>
? ? ? ? <result column="name" property="name"></result>
? ? ? ? <collection property="studentList" javaType="list" ofType="Student" column="id" select="findStudent2">
? ? ? ? ? ? <id column="id" property="id"></id>
? ? ? ? ? ? <result column="name" property="name"></result>
? ? ? ? </collection>
</resultMap>
? ? <select id="findStudent1" resultMap="GradeMap">
? ? ? ? ? ? select * from grade
? ? </select>
? ? <select id="findStudent2" resultType="Student">
? ? ? ? select id,name from student where gradeid = #{id}
? ? </select>
動態sql
? ? ? ? 我們在進行查詢操作時,有時查詢的條件不止一個,這時我們就需要在select語句中手動添加查詢條件例如:
select * from student where id = 1 and name = "小明"
? ? ? ? 前面我們將類中的屬性創建為包裝類類型,這樣當參數無效時只有兩中可能:一種是null,另一種是" ",這時我們如果不將為null或者為" "的屬性刪除的話我們就查詢不到我們想要的數據,這時我們就需要動態地將查詢條件進行改變,Mybatis框架中剛好有這種功能。我們可以將where語句的部分寫入到Mybatis提供的<where>標簽中,并將where后面的語句使用<if>標簽進行判斷。<where>標簽會動態的進行插入或刪除我們需要的或者不需要的sql語句,當<where>標簽中的<if>條件成立時<where>標簽就會將where加入到sql語句中,并且將條件成立的<if>標簽中的語句也加入到sql語句中where的后面,而且<where>還會動態地判斷where后面的第一個語句是否為and或者or,如果是還會將and和or進行刪除,當<where> 標簽中沒有一個<if>條件成立時<where>標簽就不會將where加入到sql語句中,從而實現動態sql的效果。
<select id="findStudent">
? ? select * from student
? ? <where>
? ? ? ? <if test="id!=null&id!=''">
? ? ? ? ? ? id = #{id}
? ? ? ? </if>
? ? ? ? <if test="name!=null&name!=''">
? ? ? ? ? ? and name = #{name}
? ? ? ?</if>
? ? </where>
</select>
? ? ? ? 使用<trim>標簽也可以達到這種效果,prefix="where"表示需要在語句中添加的前綴,只要有一個<if>標簽成立就加前綴,反之則一個都不加。prefixOverrides="and|or"表示當插入語句中的第一個為and或者or時就將其刪除。
<select id="findStudent">
? ? ? ? select * from student
? ? ? ? <trim prefix="where" prefixOverrides="and|or">
? ? ? ? ? ? <if test="id!=null&id!=''">
? ? ? ? ? ? ? ? id = #{id}
? ? ? ? ? ? </if>
? ? ? ? ? ? <if test="name!=null&name!=''">
? ? ? ? ? ? ? ? and name = #{name}
? ? ? ? ? ? </if>
? ? ? ? ? ? <if test="gender!=null&gender!=''">
? ? ? ? ? ? ? ? and gender = #{gender}
? ? ? ? ? ? </if>
? ? ? ? </trim>
</select>
? ? ? ? <set>標簽也是這樣如果有<if>標簽成立就插入set,如果沒有就不插入。如果插入語句的最后一個為","則<set>標簽就會將","進行刪除。
<update id="updateStudent" parameterType="Student">
? ? ? ? update student
? ? ? ? <set>
? ? ? ? ? ? <if test="name!=null&name!=''">
? ? ? ? ? ? ? ? name = #{name},
? ? ? ? ? ? </if>
? ? ? ? ? ? <if test="gender!=null&gender!=''">
? ? ? ? ? ? ? ? gender = #{gender}
? ? ? ? ? ? </if>
? ? ? ? </set>
? ? ? ? where id = #{id}
</update>
? ? ? ? <trim>標簽prefix="set"表示插入的前綴,suffixOverrides=","表示當插入語句的最后一個為","就刪除
<update id="updateStudent">
? ? ? ? update student
? ? ? ? <trim prefix="set" suffixOverrides=",">
? ? ? ? ? ? <if test="name!=null&name!=''">
? ? ? ? ? ? ? ? name = #{name},
? ? ? ? ? ? </if>
? ? ? ? ? ? <if test="gender!=null&gender!=''">
? ? ? ? ? ? ? ? gender = #{gender}
? ? ? ? ? ? </if>
? ? ? ? ? ? where id = #{id}
? ? ? ? </trim>
</update>
Mybatis的一級二級緩存
? ? ? ? 通過緩存可以減少用戶對數據庫訪問的次數,進而減少了數據庫的壓力,提高查詢性能。我們可以將通過相同的操作而得到相同的結果集的數據保存到緩存中,這樣當用戶進行多次相同的操作時就不會再向數據庫中訪問數據,而是直接通過緩存提高了查詢效率。我們一般將一段時間內不會發生改變的數據存放在緩存中,例如對某些網頁的訪問,一個網頁在一段時間內可能有很多的用戶對其進行訪問,我們不能讓用戶都去訪問數據庫中的數據,而是可以通過緩存拿到相同的數據,而緩存中的數據我們只需要讓其每過一段時間自動刷新一次即可。
? ? ? ? 一級緩存
? ? ? ? 一級緩存的作用域是同一個SqlSession中,在一個SqlSession中如果執行多次相同的sql操作,那么從第二次操作開始,讀取到的數據都是來自于緩存中的,當一個SqlSession不存在后,其對應的緩存也將被銷毀,Mybatis默認開啟的是一級緩存。
? ? ? ? 二級緩存
? ? ? ? 二級緩存是 SqlSessionFactory 級別的,作用域為同一個namespace中,當用戶執行同一個namespace中的同一個sql語句時,第一次訪問會先向數據庫中訪問并將訪問后的數據存放在二級緩存中,當第二次執行同一個namespace中的同一個sql語句時,就會從緩存中讀取數據,除非當緩存在超時,被聲明需要刷新,或者sqlSession在執行update,insert,delete操作并commit提交時,會清空緩存區,防止讀取的數據存在問題。
————————————————
版權聲明:本文為CSDN博主「楠佩憶心軒」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_52391639/article/details/125816937