在開發過程當中,最重要的就是在控制檯查看程序輸出的日誌信息,在這裏咱們選擇使用 log4j 工具來輸出:html
# Global logging configuration # 在開發環境下日誌級別要設置成 DEBUG ,生產環境設爲 INFO 或 ERROR log4j.rootLogger=DEBUG, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
其中,第一條配置語句 「log4j.rootLogger=DEBUG, stdout
」 指的是日誌輸出級別,一共有 7 個級別(OFF、 FATAL、 ERROR、 WARN、 INFO、 DEBUG、 ALL)。java
第二條配置語句 「log4j.appender.stdout=org.apache.log4j.ConsoleAppender
」 的含義是,設置名爲 stdout 的輸出端載體是哪一種類型。git
第三條配置語句 「log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
」 的含義是,名爲 stdout 的輸出載體的 layout(即界面佈局)是哪一種類型。github
第四條配置語句 「log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
」 的含義是,若是 layout 界面佈局選擇了 PatternLayout 靈活佈局類型,要指定的打印信息的具體格式。web
rn
」,UNIX 平臺爲 「n
」 %d 輸出日誌時的時間或日期,默認個事爲 ISO8601,也能夠在其後指定格式,好比 %d{yyy MMM dd HH:mm:ss},輸出相似:2018 年 4 月18 日 10:32:00 %l 輸出日誌事件的發生位置,包括類目名、發生的線程,以及在代碼中的行數在上一篇文章中,咱們講解了一個 MyBatis 的入門程序的開發,瞭解了 MyBatis 開發的基本內容。今天咱們先來了解一下 MyBatis 是如何處理多張數據庫表之間的關聯關係,其中包括:spring
首先咱們先來創建一個數據模型(刪掉以前建立的 student 表):sql
use mybatis; CREATE TABLE student ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(255) DEFAULT NULL, card_id int(11) NOT NULL, PRIMARY KEY (id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE card ( id int(11) NOT NULL AUTO_INCREMENT, number int(11) NOT NULL, PRIMARY KEY (id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO student VALUES (1,'student1',1); INSERT INTO student VALUES (2,'student2',2); INSERT INTO card VALUES (1,1111); INSERT INTO card VALUES (2,2222);
而後咱們要來確認咱們查詢的 SQL 語句,咱們或許能夠簡單的寫成下面這樣:數據庫
SELECT student.*, card.* FROM student,card WHERE student.card_id = card.id AND card.number = #{value}
肯定了主要的查詢 SQL 後,接下來咱們分別使用 resultType 和 resultMap 來實現這個一對一查詢的實例。apache
首先建立學生 student 表所對應的 Java 實體類 Student,其中封裝的屬性信息爲響應數據庫中的字段:緩存
package pojo; public class Student { int id; String name; int card_id; /* getter and setter */ }
最終咱們執行查詢(上述的 SQL 語句)的結果以下:
因爲最終的查詢的結果是由 resultType 指定的,也就是隻能映射一個肯定的 Java 包裝類,上面的 Stuent 類只包含了學生的基本信息,並無包含 Card 的信息,因此咱們要建立一個最終映射類,以 Student 類爲父類,而後追加 Card 的信息:
package pojo; public class StudentAndCard extends Student { private int number; /* getter and setter /* }
而後在 Student.xml 映射文件中定義 <select>
類型的查詢語句 SQL 配置,將以前設計好的 SQL 語句配置進去,而後指定輸出參數屬性爲 resultType,類型爲 StudentAndCard 這個 Java 包裝類:
<select id="findStudentByCard" parameterType="_int" resultType="Student"> SELECT student.*, card.* FROM student,card WHERE student.card_id = card.id AND card.number = #{value} </select>
而後在測試類中編寫測試方法:
@Test public void test() throws IOException { // 根據 mybatis-config.xml 配置的信息獲得 sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 而後根據 sqlSessionFactory 獲得 session SqlSession session = sqlSessionFactory.openSession(); // 找到身份證身份證號碼爲 1111 的學生 StudentAndCard student = session.selectOne("findStudentByCard",1111); // 得到其姓名並輸出 System.out.println(student.getName()); }
得到正確結果:
使用 resultMap 能夠將數據字段映射到名稱不同的響應實體類屬性上,重要的是,能夠映射實體類中包裹的其餘實體類。
首先咱們來建立一個封裝了 Card 號碼和 Student 實體類的 StudentWithCard 類:
package pojo; public class StudentWithCard { Student student; int number; int id; /* getter and setter */ }
SQL 語句依然沒有變化,可是使用的輸出映射屬性改成了 resultMap ,其中的映射類型是 id 爲 StudentInfoMap 的 resultMap 配置:
<select id="findStudentByCard" parameterType="_int" resultMap="StudentInfoMap"> SELECT student.*, card.* FROM student,card WHERE student.card_id = card.id AND card.number = #{value} </select> <resultMap id="StudentInfoMap" type="pojo.StudentWithCard"> <!-- id 標籤表示對應的主鍵 column 對應查詢結果的列值 property 對應封裝類中的屬性名稱 --> <id column="id" property="id"/> <result column="number" property="number"/> <!-- association 表示關聯的嵌套結果, 能夠簡單理解就是爲封裝類指定的標籤 --> <association property="student" javaType="pojo.Student"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="card_id" property="card_id"/> </association> </resultMap>
稍微修改一下測試類,測試使用 resultMap 實現的一對一查詢映射:
@Test public void test() throws IOException { // 根據 mybatis-config.xml 配置的信息獲得 sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 而後根據 sqlSessionFactory 獲得 session SqlSession session = sqlSessionFactory.openSession(); // 找到身份證身份證號碼爲 1111 的學生 StudentWithCard student = session.selectOne("findStudentByCard", 1111); // 得到其姓名並輸出 System.out.println(student.getStudent().getName()); }
測試仍然能獲得正確的結果:
仍是先來創建數據模型,刪掉以前的:
use mybatis; CREATE TABLE student ( student_id int(11) NOT NULL AUTO_INCREMENT, name varchar(255) DEFAULT NULL, PRIMARY KEY (student_id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE class ( class_id int(11) NOT NULL AUTO_INCREMENT, name varchar(255) NOT NULL, student_id int(11) NOT NULL, PRIMARY KEY (class_id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO student VALUES (1,'student1'); INSERT INTO student VALUES (2,'student2'); INSERT INTO class VALUES (1,'Java課',1); INSERT INTO class VALUES (2,'Java課',2);
name
字段表示課程的名稱。而後咱們來編寫咱們的 SQL 語句:
SELECT student.* FROM student, class WHERE student.student_id = class.student_id AND class.class_id = #{value}
咱們執行的結果以下:
咱們再來建立對應的實體類:
public class Student { private int id; private String name; /* getter and setter */ } public class Class { private int id; private String name; private List<Student> students; /* getter and setter */ }
在 Package【pojo】下新建一個【class.xml】文件完成配置:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="class"> <resultMap id="Students" type="pojo.Student"> <id column="student_id" property="id"/> <result column="name" property="name"/> </resultMap> <select id="listStudentByClassName" parameterType="String" resultMap="Students"> SELECT student.* FROM student, class WHERE student.student_id = class.student_id AND class.name= #{value} </select> </mapper>
編寫測試類:
@Test public void test() throws IOException { // 根據 mybatis-config.xml 配置的信息獲得 sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 而後根據 sqlSessionFactory 獲得 session SqlSession session = sqlSessionFactory.openSession(); // 查詢上Java課的所有學生 List<Student> students = session.selectList("listStudentByClassName", "Java課"); for (Student student : students) { System.out.println("ID:" + student.getId() + ",NAME:" + student.getName()); } }
運行測試結果,成功:
創建數據模型:
use mybatis; CREATE TABLE students ( student_id int(11) NOT NULL AUTO_INCREMENT, student_name varchar(255) DEFAULT NULL, PRIMARY KEY (student_id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE courses ( course_id int(11) NOT NULL AUTO_INCREMENT, course_name varchar(255) NOT NULL, PRIMARY KEY (course_id) )AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; CREATE TABLE student_select_course( s_id int(11) NOT NULL, c_id int(11) NOT NULL, PRIMARY KEY(s_id,c_id) ) DEFAULT CHARSET=utf8; INSERT INTO students VALUES (1,'student1'); INSERT INTO students VALUES (2,'student2'); INSERT INTO courses VALUES (1,'Java課'); INSERT INTO courses VALUES (2,'Java Web課'); INSERT INTO student_select_course VALUES(1,1); INSERT INTO student_select_course VALUES(1,2); INSERT INTO student_select_course VALUES(2,1); INSERT INTO student_select_course VALUES(2,2);
根據要求咱們來設計一下 SQL 語言:
SELECT s.student_id,s.student_name FROM students s,student_select_course ssc,courses c WHERE s.student_id = ssc.s_id AND ssc.c_id = c.course_id AND c.course_name = #{value}
執行 SQL 結果以下:
實體類雷同,就再也不贅述,咱們直接來配置映射文件【Student.xml】:
<resultMap id="Students" type="pojo.Student"> <id property="id" column="student_id"/> <result column="student_name" property="name"/> </resultMap> <select id="findStudentsByCourseName" parameterType="String" resultMap="Students"> SELECT s.student_id,s.student_name FROM students s,student_select_course ssc,courses c WHERE s.student_id = ssc.s_id AND ssc.c_id = c.course_id AND c.course_name = #{value} </select>
測試類也雷同,只須要修改一下調用的 id (改成findStudentsByCourseName)就行了,直接上測試結果:
相反也是同樣的,重要的是 SQL 語句和映射。
什麼是延遲加載?從字面上理解,就是對某一類信息的加載以前須要延遲一下子。在 MyBatis 中,一般會進行多表聯合查詢,可是有的時候不會當即用到全部的聯合查詢結果,這時候就能夠採用延遲加載的功能。
關聯查詢: SELECT orders.*, user.username FROM orders, user WHERE orders.user_id = user.id 延遲加載至關於: SELECT orders.*, (SELECT username FROM USER WHERE orders.user_id = user.id) username FROM orders
因此這就比較直觀了,也就是說,我把關聯查詢分兩次來作,而不是一次性查出全部的。第一步只查詢單表orders,必然會查出orders中的一個user_id字段,而後我再根據這個user_id查user表,也是單表查詢。
參考文章:[ 【MyBatis學習11】MyBatis中的延遲加載 ](http://www.javashuo.com/article/p-qlkugbfa-ca.html)
首先在 Mapper 映射文件中定義只查詢全部訂單信息的 SQL :
<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap"> SELECT * FROM orders </select>
上面的 SQL 語句查詢全部的訂單信息,而每一個訂單信息中會關聯查詢用戶,但因爲但願延遲加載用戶信息,因此會在 id 爲 "OrdersUserLazyLoadingResultMap
" 的 resultMap 對應的結果集配置中進行配置:
最後配置延遲加載要執行的獲取用戶信息的 SQL:
<select id="findUserById" parameterType="int" resultType="user"> select * from user where id = #{id} </select>
上面的配置會被用來延遲加載的 resultMap 中的 association 調用,輸入參數就是 association 中 column 中定義的字段信息。
在編寫測試方法以前,首先須要開啓延遲加載功能(這在 MyBatis 中默認是禁用掉的)。這須要在 MyBatis 的全局配置文件 mybatis-config.xml 中配置 setting 屬性,將延遲加載(lazyLoadingEnable)的開關設置成 「ture
」 ,而且因爲是按需加載,因此還須要將積極加載改成消極加載:
<settings> <!-- 打開延遲加載的開關 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 將積極加載改成消極加載,即延遲加載 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
什麼是 Mapper 動態代理?通常建立 Web 工程時,從數據庫取數據的邏輯會放置在 DAO 層(Date Access Object,數據訪問對象)。使用 MyBatis 開發 Web 工程時,經過 Mapper 動態代理機制,能夠只編寫數據交互的接口及方法定義,和對應的 Mapper 映射文件,具體的交互方法實現由 MyBatis 來完成。這樣大大節省了開發 DAO 層的時間。
實現 Mapper 代理的方法並不難,只須要遵循必定的開發規範便可。
咱們編寫一個使用 Mapper 代理查詢學生信息的示例,首先仍是在【pojo】下新建一個名爲 StudentMapper.xml 的 Mapper 配置文件,其中包含了對 Student 的增刪改查的 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.StudentMapper"> <!-- 查詢學生 --> <select id="findStudentById" parameterType="_int" resultType="pojo.Student"> SELECT * FROM student WHERE student_id = #{id} </select> <!-- 增長用戶 --> <insert id="insertStudent" parameterType="pojo.Student"> INSERT INTO student(student_id, name) VALUES(#{id}, #{name}) </insert> <!-- 刪除用戶 --> <delete id="deleteStudent" parameterType="_int"> DELETE FROM student WHERE student_id = #{id} </delete> <!-- 修改用戶 --> <update id="updateStudent" parameterType="pojo.Student"> UPDATE student SET name = #{name} WHERE student_id = #{id} </update> </mapper>
若是須要使用 StudentMapper.xml 的 Mapper 代理,首先須要定義一個接口,名爲 StudentMapper。而後在裏面新建四個方法定義,分別對應 StudentMapper.xml 中的 Student 的增刪改查的 SQL 配置,而後將 StudentMapper 中的 namespace 改成 StudentMapper 接口定義的地方(也就是 mapper 包下的 StudentMapper),這樣就能夠在業務類中使用 Mapper 代理了,接口代碼以下:
package mapper; import pojo.Student; public interface StudentMapper { // 根據 id 查詢學生信息 public Student findStudentById(int id) throws Exception; // 添加學生信息 public void insertStudent(Student student) throws Exception; // 刪除學生信息 public void deleteStudent(int id) throws Exception; // 修改學生信息 public void updateStudent(Student student) throws Exception; }
在測試方法中,使用 SqlSession 類的 getMapper 方法,並將要加載的 Mapper 代理的接口類傳遞進去,就能夠得到相關的 Mapper 代理對象,使用 Mapper 代理對象去對學生信息進行增刪改查:
@Test public void test() throws Exception { // 根據 mybatis-config.xml 配置的信息獲得 sqlSessionFactory String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 而後根據 sqlSessionFactory 獲得 session SqlSession session = sqlSessionFactory.openSession(); // 獲取 Mapper 代理 StudentMapper studentMapper = session.getMapper(StudentMapper.class); // 執行 Mapper 代理獨享的查詢方法 Student student = studentMapper.findStudentById(1); System.out.println("學生的姓名爲:" + student.getName()); session.close(); }
運行測試方法,看到正確的結果:
使用 Mapper 代理可讓開發更加簡潔,使查詢結構更加清晰,工程結構更加規範。
在上面的例子中,咱們已經有了方便的 Mapper 代理對象,咱們能夠進一步省掉 XML 的配置信息,進而使用方便的註解來開發 MyBatis ,讓咱們實際來操練一下:
咱們把 StudentMapper.xml 下配置的 SQL 語句經過註解的方式原封不動的配置在 StudentMapper 接口中:
public interface StudentMapper { // 根據 id 查詢學生信息 @Select("SELECT * FROM student WHERE student_id = #{id}") public Student findStudentById(int id) throws Exception; // 添加學生信息 @Insert("INSERT INTO student(student_id, name) VALUES(#{id}, #{name})") public void insertStudent(Student student) throws Exception; // 刪除學生信息 @Delete("DELETE FROM student WHERE student_id = #{id}") public void deleteStudent(int id) throws Exception; // 修改學生信息 @Update("UPDATE student SET name = #{name} WHERE student_id = #{id}") public void updateStudent(Student student) throws Exception; }
將以前配置的映射註釋掉,新建一條:
<!-- 映射文件 --> <mappers> <!--<mapper resource="pojo/StudentMapper.xml"/>--> <mapper class="mapper.StudentMapper"/> </mappers>
resource
屬性),而是類(使用 class
屬性)上面的測試代碼不用修改,直接運行,也能獲得正確結果:
更多的註解:戳這裏
在 Web 系統中,最重要的操做就是查詢數據庫中的數據。可是有些時候查詢數據的頻率很是高,這是很耗費數據庫資源的,每每會致使數據庫查詢效率極低,影響客戶的操做體驗。因而咱們能夠將一些變更不大且訪問頻率高的數據,放置在一個緩存容器中,用戶下一次查詢時就從緩存容器中獲取結果。
一級查詢存在於每個 SqlSession 類的實例對象中,當第一次查詢某一個數據時,SqlSession 類的實例對象會將該數據存入一級緩存區域,在沒有收到改變該數據的請求以前,用戶再次查詢該數據,都會從緩存中獲取該數據,而不是再次鏈接數據庫進行查詢。
第一次發出一個查詢 sql,sql 查詢結果寫入 sqlsession 的一級緩存中,緩存使用的數據結構是一個 map
同一個 sqlsession 再次發出相同的 sql,就從緩存中取不走數據庫。若是兩次中間出現 commit 操做(修改、添加、刪除),本 sqlsession 中的一級緩存區域所有清空,下次再去緩存中查詢不到因此要從數據庫查詢,從數據庫查詢到再寫入緩存。
public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session1 = sqlSessionFactory.openSession(); Category c1 = session1.selectOne("getCategory", 1); System.out.println(c1); Category c2 = session1.selectOne("getCategory", 1); System.out.println(c2); session1.commit(); session1.close(); }
運行,能夠看到第一次會去數據庫中取數據,可是第二次就不會訪問數據庫了,而是直接從session中取出來:
public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session1 = sqlSessionFactory.openSession(); Category c1 = session1.selectOne("getCategory", 1); System.out.println(c1); Category c2 = session1.selectOne("getCategory", 1); System.out.println(c2); session1.commit(); session1.close(); SqlSession session2 = sqlSessionFactory.openSession(); Category c3 = session2.selectOne("getCategory", 1); System.out.println(c3); session2.commit(); session2.close(); }
這一次,另外打開一個 session , 取一樣 id 的數據,就會發現須要執行 sql 語句,證明了一級緩存是在 session 裏的:
MyBatis 一級緩存值得注意的地方:
問題: 有些時候,在 Web 工程中會將執行查詢操做的方法封裝在某個 Service 方法中,當查詢完一次後,Service 方法結束,此時 SqlSession 類的實例對象就會關閉,一級緩存就會被清空。
二級緩存原理:
二級緩存的範圍是 mapper 級別(mapper即同一個命名空間),mapper 以命名空間爲單位建立緩存數據結構,結構是 map。
要開啓二級緩存,須要進行兩步操做。
**第一步:**在 MyBatis 的全局配置文件 mybatis-config.xml 中配置 setting 屬性,設置名爲 「cacheEnable
」 的屬性值爲 「true
」 便可:
<settings> <!-- 開啓二級緩存 --> <setting name="cacheEnabled" value="true"/> </settings>
**第二步:**而後因爲二級緩存是 Mapper 級別的,還要在須要開啓二級緩存的具體 mapper.xml 文件中開啓二級緩存,只須要在相應的 mapper.xml 中添加一個 cache 標籤便可:
<!-- 開啓本 Mapper 的 namespace 下的二級緩存 --> <cache />
開啓二級緩存以後,咱們須要爲查詢結果映射的 POJO 類實現 java.io.serializable
接口,二級緩存能夠將內存的數據寫到磁盤,存在對象的序列化和反序列化,因此要實現java.io.serializable接口。
咱們在同一個 SessionFactory 下查詢 id = 1 的數據,只有第一次須要執行 SQL 語句,從後都是從緩存中取出來的:
歡迎轉載,轉載請註明出處! 簡書ID:@我沒有三顆心臟 github:wmyskxz 歡迎關注公衆微信號:wmyskxz_javaweb 分享本身的Java Web學習之路以及各類Java學習資料