一、MyBatis概要
1.1、ORM介紹
對象關系映射(Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),用於實現面向對象編程語言里不同類型系統的數據之間的轉換。它是創建了一個可在編程語言里使用的“虛擬對象數據庫”。簡單來說ORM簡化了應用程序對數據庫的訪問,實現了將數據庫中的數據與程序中的對象進行相互映射,數據庫中的一行記錄可以對應一個對象,一個強類型的集合可以對應一張表。
1.2、常見的ORM框架與庫
自己定義的JDBCUtil缺點:沒有數據庫連接池,影響性能;功能簡單,不能應對復雜需求;沒有緩存機制;
DBUtils:Commons DbUtils是Apache組織提供的一個對JDBC進行簡單封裝的開源工具類庫,使用它能夠簡化JDBC應用程序的開發,同時也不會影響程序的性能。如果自己想學習寫框架,可以從該開源項目開始。
- Hibernate:全自動 需要些hql語句,與Struts、Spring組成SSH的搭配
- SSH(Struts2、Spring、Hibernate) S2SH
- Struts:MVC框架;Hibernate:ORM框架,冬眠;Spring:IOC、AOP…
- MyBatis:半自動 自己寫sql語句,可操作性強,小巧,最開始iBatis
- JPA:(Java Persistence API)是Sun官方提出的Java持久化規范。它為Java開發人員提供了一種對象/關系映射工具來管理Java應用中的關系數據。
1.3、MyBatis
MyBatis本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,並且改名為MyBatis 。2013年11月遷移到Github,網址:https://github.com/mybatis。
iBATIS一詞來源於“internet”和“abatis”的組合,是一個基於Java的持久層框架。iBATIS提供的持久層框架包括SQL Maps和Data Access Objects(DAO)
MyBatis 是支持普通 SQL查詢,存儲過程和高級映射的優秀持久層框架。MyBatis 消除了幾乎所有的JDBC代碼和參數的手工設置以及結果集的檢索。MyBatis 使用簡單的 XML或注解用於配置和原始映射,將接口和 Java 的POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。
每個MyBatis應用程序主要都是使用SqlSessionFactory實例的,一個SqlSessionFactory實例可以通過SqlSessionFactoryBuilder獲得。SqlSessionFactoryBuilder可以從一個xml配置文件或者一個預定義的配置類的實例獲得。
用xml文件構建SqlSessionFactory實例是非常簡單的事情。推薦在這個配置中使用類路徑資源(classpath resource),但你可以使用任何Reader實例,包括用文件路徑或file://開頭的url創建的實例。MyBatis有一個實用類----Resources,它有很多方法,可以方便地從類路徑及其它位置加載資源。
優點:
- 1. 易於上手和掌握。
- 2. sql寫在xml里,便於統一管理和優化。
- 3. 解除sql與程序代碼的耦合。
- 4. 提供映射標簽,支持對象與數據庫的orm字段關系映射
- 5. 提供對象關系映射標簽,支持對象關系組建維護
- 6. 提供xml標簽,支持編寫動態sql。
git地址:https://github.com/mybatis/
下載源碼:https://github.com/mybatis/mybatis-3/releases
下載Jar包:https://github.com/mybatis/mybatis-3
官方文檔:http://www.mybatis.org/mybatis-3/zh/index.html
MyBatis是一個支持普通SQL查詢,存儲過程和高級映射的優秀持久層框架。MyBatis消除了幾乎所有的JDBC代碼和參數的手工設置以及對結果集的檢索封裝。MyBatis可以使用簡單的XML或注解用於配置和原始映射,將接口和Java的POJO(Plain Old Java Objects,普通的Java對象)映射成數據庫中的記錄。
二、MyBatis示例
2.1、MyBatis基礎
結果:
添加包
mybatis.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"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/> <property name="username" value="tax"/> <property name="password" value="orcl"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/tax/mapping/bookMapping.xml"/> </mappers> </configuration>
Book.java POJO對象
package com.tax.model; /**圖書實體 Bean POJO*/ public class Book { /**編號*/ private int id; /**書名*/ private String title; /**類型*/ private String typename; /**價格*/ private Double price; /**狀態*/ private String state; @Override public String toString() { return "Book{" + "id=" + id + ", title='" + title + '\'' + ", typename='" + typename + '\'' + ", price=" + price + ", state='" + state + '\'' + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getTypename() { return typename; } public void setTypename(String typename) { this.typename = typename; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
bookMaping.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="com.tax.mapping.bookMapper"> <!-- 根據id查詢得到一個book對象 --> <select id="getBookById" resultType="com.tax.model.Book"> select id, title, typename, price, state from book where id=#{id} </select> <select id="getAllBooks" resultType="com.tax.model.Book"> select id, title, typename, price, state from book </select> </mapper>
BookDao 數據訪問
package com.tax.dao; import com.tax.model.Book; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; /**圖書數據訪問*/ public class BookDao { /**獲得圖書通過編號*/ public Book getBookById(int id){ //將mybatis的配置文件轉換成輸入流,讀配置文件 InputStream cfg=this.getClass().getClassLoader().getResourceAsStream("mybatis.xml"); //根據配置文件構建會話工廠 SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(cfg); //創建會話 SqlSession session=factory.openSession(); //調用方法getBookById帶入參數1獲得單個圖書對象 Book book=session.selectOne("com.tax.mapping.bookMapper.getBookById",id); //關閉會話 session.close(); return book; } public static void main(String[] args) { BookDao dao=new BookDao(); //輸出 System.out.println(dao.getBookById(3)); } }
運行結果:
Book{id=3, title='Java並發編程的藝術', typename='軟件工程', price=45.4, state='未借出'}
2.2、JUnit單元測試
添加JUnit依賴
添加測試的包與類
package com.tax.test; import com.tax.dao.BookDao; import com.tax.model.Book; import org.junit.Assert; import org.junit.Test; import org.junit.Assert.*; public class BookDaoTest { @Test public void getBookById(){ BookDao dao=new BookDao(); Book book=dao.getBookById(1); System.out.println(book); //斷言,期待1,實際2 //Assert.assertEquals(1,2); //不為空就通過 Assert.assertNotNull(book); } }
其它注解
import static org.junit.Assert.assertEquals; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; public class CalculatorTest { private static Calculator calculator = new Calculator(); //每個方法測試前調用 @Before public void clearCalculator() { calculator.clear(); } //每個方法測試完以后調用 @After public void tearDown() { } //@Test:測試方法,表明這是一個測試方法。在Junit中將會自動被執行。 @Test public void add() { calculator.add(1); calculator.add(1); //第一個參數是預期的,第二個參數是真實的 assertEquals(calculator.getResult(), 2); } @Test public void subtract() { calculator.add(10); calculator.substract(2); //第一個參數是預期的,第二個參數是真實的 assertEquals(calculator.getResult(), 8); } //給測試函數設定一個執行時間,超過了這個時間(400毫秒),它們就會被系統強行終止 @Test(timeout=400) public void divide() { calculator.add(8); calculator.divide(2); //第一個參數是預期的,第二個參數是真實的 assert calculator.getResult() == 5; } //使用注釋來聲明該異常是預期的,異常測試是Junit4中的最大改進 @Test(expected = ArithmeticException.class) public void divideByZero() { calculator.divide(0); } //@Ignore:忽略的測試方法,標注的含義就是“某些方法尚未完成,暫不參與此次測試” @Ignore("not ready yet") @Test public void multiply() { calculator.add(10); calculator.multiply(10); //第一個參數是預期的,第二個參數是真實的 assertEquals(calculator.getResult(), 100); } }
注解
@Test 測試方法,表明這是一個測試方法。在Junit中將會自動被執行。
@Test(timeOut=400) 給測試函數設定一個執行時間,超過了這個時間(400毫秒),它們就會被系統強行終止
@Test(expected = ArithmeticException.class) 使用注釋來聲明該異常是預期的,異常測試是Junit4中的最大改進
@Ignore("not ready yet") 忽略的測試方法,標注的含義就是“某些方法尚未完成,暫不參與此次測試”
@Before 每個方法測試前調用
@After 每個方法測試完以后調用
@BeforeClass 每個類運行前調用,並且只調用一次
@AfterClass 每個類運行后調用,並且只調用一次
2.3、XML+接口實現CRUD
BookDao接口
package com.tax.dao2; import com.tax.model.Book; import java.util.List; public interface BookDao { public Book getBookById(int id); public List<Book> getAllBooks(); public int add(Book book); public int update(Book book); public int deleteById(int id); }
MyBatis工具類
package com.tax.dao2; import java.io.InputStream; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public abstract class MyBatisUtil { //GC不理static private static SqlSessionFactory factory=null; public static SqlSessionFactory getSqlSessionFactory(){ if(factory==null){ // 獲得環境配置文件流 InputStream config = MyBatisUtil.class.getClassLoader().getResourceAsStream("mybatis.xml"); // 創建sql會話工廠 factory = new SqlSessionFactoryBuilder().build(config); } return factory; } //獲得會話 public static SqlSession getSession(){ return getSqlSessionFactory().openSession(true); } /** * 獲得得sql會話 * @param isAutoCommit 是否自動提交,如果為false則需要sqlSession.commit();rollback(); * @return sql會話 */ public static SqlSession getSession(boolean isAutoCommit){ return getSqlSessionFactory().openSession(isAutoCommit); } }
Book表映射文件
<?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.tax.dao2.BookDao"> <!-- 根據id查詢得到一個book對象 --> <select id="getBookById" resultType="Book"> select id, title, typename, price, state from book where id=#{id} </select> <select id="getAllBooks" resultType="Book"> select id, title, typename, price, state from book </select> <insert id="add" parameterType="Book"> insert into book (id, title, typename, price, state) values(seq_book_id.nextval, #{title}, #{typename}, #{price}, #{state}) </insert> <update id="update" parameterType="Book"> update book set title=#{title},typename=#{typename},price=#{price},state=#{state} where id=#{id} </update> <delete id="deleteById"> delete from book where id=#{id} </delete> </mapper>
BookDaoImpl實現
package com.tax.dao2; import com.tax.model.Book; import org.apache.ibatis.session.SqlSession; import java.util.List; public class BookDaoImpl implements BookDao { @Override public Book getBookById(int id) { SqlSession session = null; try { session = MyBatisUtil.getSession(); BookDao dao = session.getMapper(BookDao.class); return dao.getBookById(id); } finally { session.close(); } } @Override public List<Book> getAllBooks() { SqlSession session = null; try { session = MyBatisUtil.getSession(); BookDao dao = session.getMapper(BookDao.class); return dao.getAllBooks(); } finally { session.close(); } } @Override public int add(Book book) { SqlSession session = null; try { session = MyBatisUtil.getSession(); BookDao dao = session.getMapper(BookDao.class); return dao.add(book); } finally { session.close(); } } @Override public int update(Book book) { SqlSession session = null; try { session = MyBatisUtil.getSession(); BookDao dao = session.getMapper(BookDao.class); return dao.update(book); } finally { session.close(); } } @Override public int deleteById(int id) { SqlSession session = null; try { session = MyBatisUtil.getSession(); BookDao dao = session.getMapper(BookDao.class); return dao.deleteById(id); } finally { session.close(); } } }
測試文件
package test.com.tax.dao2; import com.tax.dao2.BookDao; import com.tax.dao2.BookDaoImpl; import com.tax.model.Book; import org.junit.Assert; import org.junit.Test; import org.junit.Before; import org.junit.After; /** * BookDaoImpl Tester. * * @author <Authors name> * @version 1.0 * @since <pre>03/15/2018</pre> */ public class BookDaoImplTest { BookDao dao; @Before public void before() throws Exception { dao = new BookDaoImpl(); } @After public void after() throws Exception { } /** * Method: getBookById(int id) */ @Test public void testGetBookById() throws Exception { System.out.println(dao.getBookById(3)); } /** * Method: getAllBooks() */ @Test public void testGetAllBooks() throws Exception { for (Book book:dao.getAllBooks()) { System.out.println(book); } } /** * Method: add(Book book) */ @Test public void testAdd() throws Exception { Book book=new Book(); book.setTypename("計算機"); book.setState("未借出"); book.setTitle("Spring 入門到放棄"); book.setPrice(23.5); Assert.assertEquals(1,dao.add(book)); } /** * Method: update(Book book) */ @Test public void testUpdate() throws Exception { Book book=dao.getBookById(49); book.setTypename("從入門到精通"); Assert.assertEquals(1,dao.update(book)); } /** * Method: deleteById(int id) */ @Test public void testDeleteById() throws Exception { Assert.assertEquals(1,dao.deleteById(49)); } }
結果:
2.4、基於注解實現CRUD
使用注解不再需要mapping文件
BookDao接口如下:
package com.tax.dao; import com.tax.model.Book; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import java.util.List; public interface BookDao { @Select("select id, title, typename, price, state from book where id=#{id}") public Book getBookById(int id); @Select("select * from book") public List<Book> getAllBooks(); @Insert("insert into book (id, title, typename, price, state) values(seq_book_id.nextval, #{title}, #{typename}, #{price}, #{state})") public int add(Book book); @Update("update book set title=#{title},typename=#{typename},price=#{price},state=#{state} where id=#{id}") public int update(Book book); @Delete("delete from book where id=#{id}") public int deleteById(int id); }
mybatis.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"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/> <property name="username" value="tax"/> <property name="password" value="orcl"/> </dataSource> </environment> </environments> <mappers> <mapper class="com.tax.dao.BookDao"/> </mappers> </configuration>
其它內容與2.3節一樣。
2.5、別名與數據源
bookMapping.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="com.tax.dao2.BookDao"> <!-- 根據id查詢得到一個book對象 --> <select id="getBookById" resultType="B"> select id, title, typename, price, state from book where id=#{id} </select> <select id="getAllBooks" resultType="B"> select id, title, typename, price, state from book </select> <insert id="add" parameterType="Book"> insert into book (id, title, typename, price, state) values(seq_book_id.nextval, #{title}, #{typename}, #{price}, #{state}) </insert> <update id="update" parameterType="Book"> update book set title=#{title},typename=#{typename},price=#{price},state=#{state} where id=#{id} </update> <delete id="deleteById"> delete from book where id=#{id} </delete> </mapper>
mybatis.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"> <configuration> <typeAliases> <typeAlias type="com.tax.model.Book" alias="B"></typeAlias> <package name="com.tax.model"></package> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/> <property name="username" value="tax"/> <property name="password" value="orcl"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/tax/mapping/bookMapping.xml"/> </mappers> </configuration>
數據源
db.properties
driver=oracle.jdbc.driver.OracleDriver url=jdbc:oracle:thin:@127.0.0.1:1521:orcl username=tax password=orcl
mybatis.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"> <configuration> <properties resource="db.properties"></properties> <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 class="com.tax.dao.BookDao"/> </mappers> </configuration>
2.6、Java Web中整合MyBatis(展示與刪除功能)
項目結構:
BookServlet
package com.tax.action; import com.tax.dao.BookDao; import com.tax.dao.BookDaoImpl; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/BookServlet") public class BookServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //設置編碼 request.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=utf-8"); response.setCharacterEncoding("utf-8"); //獲得action類型 String act=request.getParameter("act"); //如果當前的動作是刪除 if(act.equals("delete")) { //獲得URL中要刪除的圖書編號 int id=Integer.parseInt(request.getParameter("id")); BookDao dao=new BookDaoImpl(); if(dao.deleteById(id)>0){ //執行刪除並成功 request.setAttribute("msg","刪除成功!"); }else{ request.setAttribute("msg","刪除失敗!"); } //轉發到index.jsp頁面 request.getRequestDispatcher("index.jsp").forward(request,response); }else if(act.equals("edit")){ }else{ response.sendRedirect("index.jsp"); } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } }
index.jsp
<%@ page import="com.tax.dao.BookDao" %> <%@ page import="com.tax.dao.BookDaoImpl" %> <%@ page import="com.tax.model.Book" %> <%@ page import="java.util.ArrayList" %> <%@ page import="java.util.List" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% BookDao dao = new BookDaoImpl(); List<Book> books = dao.getAllBooks(); %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>圖書列表</title> <style> body{ font-size:14px; } #tabBooks { width: 80%; } #tabBooks, #tabBooks td { border-collapse: collapse; } .red{ color:red; } .green{ color:green; } </style> </head> <body> <h2>圖書列表</h2> <table border="1" id="tabBooks"> <tr> <td> <input type="checkbox" id="chbAll"/> </td> <td>序號</td> <td>書名</td> <td>價格</td> <td>類型</td> <td>狀態</td> <td>操作</td> </tr> <%for (int i = 0; i < books.size(); i++) {%> <tr> <td> <input type="checkbox" name="id" value=""/> </td> <td> <%=i + 1%> </td> <td><%=books.get(i).getTitle()%> </td> <td><%=books.get(i).getPrice()%> </td> <td><%=books.get(i).getTypename()%> </td> <td class="<%=books.get(i).getState().equals("未借出")?"green":"red"%>"> <%=books.get(i).getState()%> </td> <td> <a href="BookServlet?act=delete&id=<%=books.get(i).getId()%>" class="delete" onclick="return isDel()">刪除</a> </td> </tr> <%}%> </table> <script> // var items=document.querySelectorAll(".delete"); // for(var i=0;i<items.length;i++){ // items[i].onclick=function () { // return confirm("您確定要刪除嗎?"); // } // } function isDel() { return confirm('您確定要刪除嗎?'); } </script> </body> </html>
結果:
2.7、參考代碼
https://git.coding.net/zhangguo5/WebReadMyBatis.git
三、MyBatis總結
使用jdbc開發時,和mybatis相比的不足
1,數據庫連接,使用時就創建,不使用就釋放,對數據庫進行頻繁連接開關和關閉,造成數據庫資源浪費,影響數據庫的性能
解決:使用數據庫連接池管理數據庫的連接
2,sql語句使用硬編碼在java程序中,修改sql語句,就需要重新編譯java代碼,不利於系統維護
解決:把sql語句放在xml配置文件中,修改sql語句也不需要重新編譯java代碼
3,向預編譯語句PreparedStatement中設置參數,對占位符位置和設置參數值,硬編碼,修改sql語句也不需要重新編譯java代碼
解決:把sql語句和占位符設置參數值放在xml配置文件中
4,從result中遍歷結果集數據時,存在硬編碼,將獲取表的字段進行硬編碼
解決:將查詢的結果集,自動映射成 java對象
二 mybatis框架,是一個持久層框架,是apache下的頂級項目
mybatis讓程序員將主要精力放在sql上,通過mytabis提供的映射方式,自動生成滿足需要的sql語句
mybatis可以向PreparedStatement中輸入參數自動進行輸入映射,將查詢結果集靈活的映射成Java對象(輸出映射),輸入映射和輸出映射這是mybatis的核心
mybatis框架執行流程圖
三 mybatis的工作環境搭建和架構示意圖
四 mybatis的開發
1.映射文件的開發如下圖
2,映射文件配置好了之后,還需要在全局配置文件sqlMapConfig.xml中添加映射文件
3,sqlsession會話去執行操作查詢數據庫映射文件,下圖中的錯誤糾正為’%${value}%’
查詢出的是單條記錄使用selectOne,下圖中的錯誤糾正為把“1”改為int類型的1
sqlsession.selectOne(“test.findUserById”, 1);
查詢出的是多條記錄使用selectList
sqlsession.selectList(“test.findUserByName”, “hello”);
4,添加用戶映射文件配置如下:
程序代碼:
5,總結:
四 mybatis開發dao方法
mybatis的配置文件不變
1,先使用原型的開發dao方法
開發接口
2, 開發接口實現
3, 測試代碼
4,總結
五 mybatis利用mapper代理開發dao(重點掌握)
mapper代理開發,就不需要接口的實現類,只需要接口UserMapper.java和映射文件UserMapper.xml就可以了,但是遵循一定的開發規范:
1,在UserMapper.xml文件中namespace等於UserMapper接口地址
2,UserMapper.java接口中的方法名要和UserMapper.xml中的statement的id一致
3,UserMapper.java接口中的方法輸入參數要和UserMapper.xml中的statement的parameterType指定的類型一致
4,UserMapper.java接口中的方法的返回值類型要和UserMapper.xml中的statement的resultType指定的類型一致
測試代碼:
上圖畫線區域:這里沒有實現接口的實現類,而是使用mybatis生成的代理對象來生成UserMappper接口的對象,從而能夠調用其方法
mapper代理開發dao出現的問題總結:
1,代理對象內部調用selectOne或selectList
如果mapper方法返回單個pojo對象(非集合對象),代理對象內部通過selectOne查詢數據庫,也可以使用selectList查詢。
如果mapper方法返回集合對象,代理對象內部通過selectList查詢 數據庫,不能使用selectOne查詢,否則會出錯。
問題是: 編譯期間不會報錯,二者容易寫錯使用。
2,mapper接口方法參數只有一個
根據規范編寫的代理對象的傳入參數只能有一個(mapper.xml文件中的parameterType參數只有一個),不利於系統的擴展
解決:即使mapper接口中只有一個參數,可以使用包裝類型的pojo滿足不同的業務方法需求
mybatis的一些細節剖析:
1,全局配置文件sqlMapConfig.xml中配置內容如下:
-
properties(屬性)
注意:mybatis將按照下面的順序來加載屬性:
(1)在properties元素體內定義的屬性首先被讀取。(可以在此屬性中加入jdbc的配置文件db.properties),在sqlMapConfig.xml中就不需要對數據庫連接參數進行硬編碼了。 -
settings全局參數設置
mybatis框架運行時可以調整一些運行參數,會影響mybatis運行行為,所以建議不要隨便修改
比如:二級緩存,開啟延時加載。。。 - typeAliases(別名) 重點掌握
<typeAliases> <!--針對單個別名定義 type:類型的路徑 alias:別名 --> <typeAlias type="com.jary.mybatis.po.User" alias="user" /> <!--還可以進行批量別名定義 指定包名,mybatis自動掃描包中的po類 --> <package name="com.jary.mybatis.po" /> </typeAliases>
上面的別名定義后,在mapper.xml中就可以這樣使用了
user代替了輸出結果類型com.jary.mybatis.po.User。
4.映射文件(mapper)
通過resource加載單個的映射文件
<mapper resource="mapper/UserMapper.xml" />
- 1
通過mapper接口加載單個mapper,要遵循一定的規范:
(1)前提是使用mapper代理開發(已經有4個規范)
(2)需要將mapper接口類名和mapper.xml映射文件名稱保持一致,且在同一目錄下
<mapper class="com.jary.mybatis.mapper.UserMapper" />
- 1
通過批量加載mapper(推薦使用):實現條件是
要滿足mapper接口加載映射文件和使用mapper代理開發同時滿足
mybatis的一些細節剖析結束
mybatis的核心輸入映射和輸出映射開始:
輸入映射
通過parameterType指定輸入參數類型,類型可以是簡單類型、hashmap、pojo的包裝類型
1,傳遞pojo的包裝對象
(1)需求
完成用戶信息的綜合查詢,需要傳入查詢條件復雜(可能包括用戶信息,商品信息,商品訂單等),這樣靠一個parameterType只能傳入一個輸入參數,所有需要pojo的包裝類型來實現
(2)定義包裝類型pojo
針對上面的需求,在包裝類型的pojo中把這些復雜的查詢條件包裝進去,定義包裝類UserQueryVo,把需要查詢的條件全部定義在里面
上圖中標注的用戶查詢條件使用的是User的擴展類(因為User類一般是由逆向工程自動生成的,不要進行修改,所有使用的擴展類來實現)
映射文件UserMapper.xml配置
UserMapper.java接口文件的配置
上圖中,把包裝類作為參數傳入,返回值是一個用戶列表所以用list集合接收
測試代碼如下圖:
輸出映射
1,resultType
使用resultType進行輸出映射時,只有查詢輸出結果列名和pojo中的屬性名一致才可以,映射成功
如果查詢出來的列名和pojo中的屬性名沒有一個一致的,就不會創建pojo對象
如果查詢出來的列名和pojo中的屬性名有一個一致,就會創建pojo對象
輸出pojo對象和pojo列表
不管是輸出的pojo單個對象還是一個列表(list中包含pojo),在mapper.xml中resultType指定的類型是一樣的
在mapper.java指定的方法返回值類型不一樣:
(1)輸出單個pojo對象,方法返回值是個單個對象類型
(2)輸出pojo對象list,方法返回值就是list對象類型
在動態代理對象中,是根據mapper方法的返回值類型來確定是調用selectOne(返回單個對象)還是selectList(返回集合對象)
2,resultMap
使用resultMap進行映射時,查詢結果列名和pojo的屬性名不一致時,resultMap會對列名和pojo屬性名進行映射,保證其成功映射
使用resultMap需要這二步:
(1)定義resultMap
(2)使用resultMap作為statement的輸出映射類型
mybatis的核心輸入映射和輸出映射結束:
mybatis的動態sql和sql片段開始:
動態sql
mybatis核心就是對sql語句進行靈活操作,通過表達式進行判斷,對sql進行靈活拼接和組裝。
需求:用戶信息查詢的綜合信息需要使用動態sql
對查詢條件進行判斷,如果出入參數不為空,才進行拼接
測試代碼需要注意的是如下圖:
如果不設置某個值,條件將不拼接在sql中
sql片段
需求:將上面的動態sql(重復的sql語句)抽取出來做成一個片段,方便其他statement語句重復使用sql片段
使用sql片段
使用foreach標簽遍歷
給sql傳入數組或者集合時,mybatis使用foreach解析
需求:在用戶信息的綜合查詢中增加多個id傳入
sql語句如下:
select * from user where id=1 or id=3 or id=5
也可以使用select * from user where id in(1,3,5)
mybatis的動態sql和sql片段結束
mybatis高級映射開始:
一、 高級映射一對一查詢(使用到assocition標簽實現關聯對象的一對一查詢映射),分別使用resultType和resultMap,並且比較二者的區別
還有一點就是,resultType查詢關聯列表結果列如果和pojo屬性名不一致,需要自己創建擴展類(繼承包括結果集列名多的pojo對象,這樣可以少寫一點屬性名)。resultMap則不需要創建擴展類,而是把關聯信息的對象注入,從而實現結果集列名和pojo屬性名保持一致。
二、高級映射一對多查詢(使用collection標簽來實現關聯對象的一對多查詢映射),一對多,就是關聯的對象查詢結果是一個List集合
開發步驟:
(1)首先寫sql語句
需求:查詢訂單及訂單明細信息(一個訂單包含多個訂單明細,所以一對多)
主表:訂單表
關聯表:訂單明細表
經過之前一對一查詢(用戶的訂單)的分析,我們只需要在此基礎上關聯訂單明細表即可。
(2)pojo類(resultType時用到擴展類,這里我們使用resultMap不需要包裝類)
(3)mapper.xml(這里我們使用了resultMap的繼承,不需要重新關聯訂單表和用戶表,通過繼承之前的一對一(用戶查詢訂單)所寫的二個表,可以減少大量的重復代碼。同時使用了collection集合標簽將關聯查詢出的多條記錄映射到List集合中)
(4)mapper.java
一對多查詢總結:
mybatis使用resultMap的collection(resultType沒有此標簽)對關聯查詢的多條記錄映射到一個list集合中。
使用resultType通過雙重循環遍歷可以實現,去除重復記錄,將訂單明細映射在orderdetail中(了解)
多對多查詢實例和上邊類似。主要是搞清楚各個表之間的對應關系,訂單的collection中嵌套訂單明細的collection,而訂單明細的collection中嵌套商品信息。
mybatis高級映射結束
mybatis的延遲加載和緩存技術開始
mybatis一級緩存
mybatis的二級緩存
mybatis默認是沒有開啟二級緩存的。
開啟二級緩存需要在mybatis的全局配置文件sqlMapConfig.xml中加入
除了開啟二級緩存開關外,還需要在各自的mapper.xml中開啟二級緩存。
原理圖:
如上圖:sqlsession1去查詢id為1的用戶信息,查詢到用戶信息就會查詢數據存放在二級緩存區域(hashmap)中
sqlsession2去查詢id為1的用戶信息,首先去緩存中查找是否存在數據,如果存在就直接從二級緩存中取出數據。
二級緩存和一級緩存的區別:二級緩存的范圍更大,多個sqlsession
可以共享usermapper的二級緩存。
二級緩存是根據mapper的namespace來划分的,相同namaspace下的mapper共享二級緩存,反之
如果sqlsession3去執行相同mapper下sql,並執行commit()操作,則清空該命名空間下的二級緩存
二級緩存的測試代碼:
上面塗黃部分要特別注意,sqlsession關閉時才可以把數據寫到二級緩存區域中,如果本namespace下的sqlsession執行了commit()操作,二級緩存就會清空
禁用二級緩存
也可以禁用單個查詢的二級緩存,這樣要保證每次查詢的都是最新數據。
刷新二級緩存(就是清空緩存,切記)
總結:一般情況下,執行commit()操作之后,都要刷新緩存,因此flushCache都設為true,來避免數據的臟讀。
mybatis cache的參數設置
flushInterval(刷新間隔),可以為任意整數,單位為毫秒值,這個比較有用。
mybatis和第三方分布式緩存框架整合(ehcache,redis,memcache)
mybatis在緩存方面還是比較弱,特別是分布式緩存不支持
我們的系統為了提高系統並發,性能,一般對系統進行分布式部署(集群部署方式)
整合方法
mybatis提供了一個cache接口,如果要實現自己的緩存邏輯,實現cache接口即可
mybatis和ehcache整合,mybatis和ehcache整合包中提供了一個cache接口的實現類。
二級緩存的應用場景(實際開發中用 刷新間隔)
二級緩存額局限性
細粒度緩存,就是修改一個商品信息(即執行commit()方法),只修改緩存中這一個商品的信息,其余的信息不清空。
mybatis的延遲加載和緩存技術結束
mybatis和spring的整合(重點掌握)開始
1、整合的思路:
(1)首先需要spring通過單例方式管理sqlSessionFactory
(2)spring和mybatis整合生成代理對象,使用SqlSessionFactory創建sqlSession會話(此步是由spring和mybatis整合自動完成)
(3)持久層的mapper,dao都需要由spring來管理
2、環境的搭建
上面的sqlmap目錄下的User.xml是為了原始dao開發使用的。還要加載spring,mybatis,mybatis-srping等jar包。
3、在spring的配置文件中配置sqlSessionFactory和數據源
sqlSessionFactory在mybatis和spring的整合包下
上圖中:使用C3P0配置數據庫連接池,屬性參數名要按照規定寫,不能自己定義,否則會報錯,而使用dbcp就可以自定義參數名,這點注意。
在加載配置文件時,都要加上類路徑名classpath
在使用原始dao開發時,屬性name值要與UserDaoImpl類中變量名一致(特別是大小寫)
4、*原始Dao的開發(和spring整合后)*
4.1 User.xml(也稱mapper.xml更准確)
和spring整合后,需要使用spring來管理mapper,spring配置文件為applicationContext.xml
還有mybatis的配置文件來加載User.xml
4.2 UserDAO
基本上不用改變
4.3 UserDaoImpl(重點在Dao的實現類上)
上圖中的代碼最重要的就是繼承了SqlSessionDaoSupport通過this.getSqlSession()來得到SqlSession會話
這里也不需要寫sqlSession的事務提交(更新操作不用寫)和sqlSession關閉
4.4 測試代碼
5、使用mapper代理來開發 (mybatis和spring整合后)
和利用原始dao開發差不多,只是不需要dao接口的實現類
而是根據一個規范來實現dao接口生成代理對象
5.1規范:
(1)mapper.xml中的namespace命名空間等於mapper.java的全路徑名
(2)mapper.xml和mapper.java應在同一個目錄下
(3)mapper.xml中的statement語句的輸入參數paramType類型應與mapper.java中方法中傳遞的參數類型一致
(4)mapper.xml中的statement語句的輸出參數resultType類型應與mapper.java中方法返回值類型一致
5.2 讓spring來管理mapper,在配置文件中
重點在這里,使用mybatis和spring的整合包中MapperFactoryBean來實現mapper接口生成代理對象
屬性值有mapperInterface和sqlSessionFactory
總結:此方法存在一個大問題,需要針對每個mapper進行配置,太麻煩
終極解決方案: 通過mapper批量掃描,從mapper包中掃描出mapper接口,自動創建代理對象並且在spring容器中注冊
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
- 1
上面代碼,不能使用ref而是使用value,剛開始用錯了。
開發中推薦使用自動掃描,
mybatis和spring的整合(重點掌握)結束
mybatis的逆向工程(了解會用就行)
generator.xml的配置,這里要記住,上圖中最下邊table后面的一長串值等於false的屬性,是為了不生成其他與我們無關的example等代碼
下面需要mybatis-generator-core-1.3.2.jar和generator.xml文件在同於目錄下,並且建立src目錄接收生成的文件
生成后的如下圖
沒有了example的無用類了,比較干凈,推薦使用
1.接口綁定:兩種方法,基於注解或者基於xml文檔mapper,但要注意mapper的namespace要與接口路徑完全一致。
2.orm格式轉換:通過設置resultMap和ResultType,將數據庫中的記錄轉換為代碼的bean對象。得到list或者對象。
3.通過parameterType接收參數,進行動態sql生成。運用ognl表達式
4.走緩存,設置二級緩存。設置二級緩存源。
5.為什么要通過orm框架來走緩存呢?因為自己配置緩存策略相對復雜,比如當insert/update/delete時,要清除相應的緩存。當某些情況select又要添加進緩存。
6.orm框架,orm框架,它是怎么進行對象和數據庫中表的轉換的呢?答:數據庫中的表要與代碼中的類一一對應,包括屬性。這樣不就能進行匹配轉換了嘛。
5.返回list,必須要配置resultMAp
6.insert操作時,要注意主鍵 主鍵生成策略,要設置useGeneraterKey = true,和 keyProperty="id",指定哪個是主鍵
<insert id="addUser" parameterType="User" useGeneratedKeys="true" keyProperty="id"> insert into user(userName,userAge,userAddress) values(#{userName},#{userAge},#{userAddress}) </insert>
7.spring在於mybatis集成時,spring負責什么呢?誰來維護datasource,誰來建立sqlSessionFactory?
答:spring作為多個框架的粘合劑,spring負責建立datasource,sqlsessionFactpry。充分利用spring的ioc和aop功能。
spring 配置文件:applicationContext.xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">//spring管理配置datasource <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> //spring管理配置sqlsessionFactory <!--dataSource屬性指定要用到的連接池--> <property name="dataSource" ref="dataSource"/> <!--configLocation屬性指定mybatis的核心配置文件--> <property name="configLocation" value="config/Configuration.xml"/> </bean>
8.Mybatis的dao實現接口(接口綁定),是由mybatis來實現的,那又怎么來使用這個實現類呢?
答:當然是注冊到spring里了,作為一個bean使用。
即:mybatis的dao實現類,也都要注冊到spring的ioc容器中,以便利用spring的ioc和aop功能。
注意此時dao實現類,的具體類是mybatis固定的org.mybatis.spring.mapper.MapperFactoryBean類,這個類專門用來生成具體的dao實現類。
但要記住,一切的增刪改查都是通過session來進行的,所以dao實現類要di依賴注入sqlSessionFactory這個屬性。
再利用mapperInterface指定具體的mapper接口類。
spring 配置文件:applicationContext.xml
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <!--sqlSessionFactory屬性指定要用到的SqlSessionFactory實例--> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> <!--mapperInterface屬性指定映射器接口,用於實現此接口並生成映射器對象--> <property name="mapperInterface" value="com.yihaomen.mybatis.inter.IUserOperation" /> </bean>
9.即:mybatis的dao實現類,也都要注冊到spring的ioc容器中,以便利用spring的ioc和aop功能。
10.spring的配置文件applicationContext.XML負責配置與數據庫相關,與mybatis sqlSessionFaction 整合,掃描所有mybatis mapper 文件等相關內容。
事務管理器也在spring的配置文件中配置,同時要依賴注入datasource屬性
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
11.mybatis的分頁功能,可以自己利用mysql代碼實現,也可以利用mybatis分頁插件,如pageHelper
12.mybatis的傳入參數只能是一個,只能有1個。可以是各種Java的基本數據類型:包含int,String,Date等。基本數據類型作為傳參,只能傳入一個。通過#{參數名} 即可獲取傳入的值 ,復雜數據類型:包含JAVA實體類、Map。通過#{屬性名}或#{map的KeyName}即可獲取傳入的值,但是如果想傳入一個collection怎么辦呢?
經查找后發現可以使用mapper配置文件中的foreach語句,借用別人寫的文章:
13. foreach
對於動態SQL 非常必須的,主是要迭代一個集合,通常是用於IN 條件。List 實例將使用“list”做為鍵,數組實例以“array” 做為鍵。
foreach元素是非常強大的,它允許你指定一個集合,聲明集合項和索引變量,它們可以用在元素體內。它也允許你指定開放和關閉的字符串,在迭代之間放置分隔符。這個元素是很智能的,它不會偶然地附加多余的分隔符。
14. 在Java實體對象對中,一對多可以根據List和Set來實現,兩者在mybitis中都是通過collection標簽來配合使用
15.
一對一關聯
根據班級id查詢班級信息(帶老師的信息) 10 ##1. 聯表查詢 11 SELECT * FROM class c,teacher t WHERE c.teacher_id=t.t_id AND c.c_id=1;
<select id="getClass" parameterType="int" resultMap="ClassResultMap"> 24 select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id} 25 </select> 26 <!-- 使用resultMap映射實體類和字段之間的一一對應關系 --> 27 <resultMap type="me.gacl.domain.Classes" id="ClassResultMap"> 28 <id property="id" column="c_id"/> 29 <result property="name" column="c_name"/> 30 <association property="teacher" javaType="me.gacl.domain.Teacher"> 31 <id property="id" column="t_id"/> 32 <result property="name" column="t_name"/> 33 </association> 34 </resultMap>
<!-- 37 方式二:嵌套查詢:通過執行另外一個SQL映射語句來返回預期的復雜類型 38 SELECT * FROM class WHERE c_id=1; 39 SELECT * FROM teacher WHERE t_id=1 //1 是上一個查詢得到的teacher_id的值 40 --> 41 <select id="getClass2" parameterType="int" resultMap="ClassResultMap2"> 42 select * from class where c_id=#{id} 43 </select> 44 <!-- 使用resultMap映射實體類和字段之間的一一對應關系 --> 45 <resultMap type="me.gacl.domain.Classes" id="ClassResultMap2"> 46 <id property="id" column="c_id"/> 47 <result property="name" column="c_name"/> 48 <association property="teacher" column="teacher_id" select="getTeacher"/> 49 </resultMap> 50 51 <select id="getTeacher" parameterType="int" resultType="me.gacl.domain.Teacher"> 52 SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id} 53 </select>
MyBatis中使用association標簽來解決一對一的關聯查詢,association標簽可用的屬性如下:
- property:對象屬性的名稱
- javaType:對象屬性的類型
- column:所對應的外鍵字段名稱
- select:使用另一個查詢封裝的結果
一對一關聯
根據班級id查詢班級信息(帶老師的信息) 10 ##1. 聯表查詢 11 SELECT * FROM class c,teacher t WHERE c.teacher_id=t.t_id AND c.c_id=1;
<select id="getClass" parameterType="int" resultMap="ClassResultMap"> 24 select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id} 25 </select> 26 <!-- 使用resultMap映射實體類和字段之間的一一對應關系 --> 27 <resultMap type="me.gacl.domain.Classes" id="ClassResultMap"> 28 <id property="id" column="c_id"/> 29 <result property="name" column="c_name"/> 30 <association property="teacher" javaType="me.gacl.domain.Teacher"> 31 <id property="id" column="t_id"/> 32 <result property="name" column="t_name"/> 33 </association> 34 </resultMap>
<!-- 37 方式二:嵌套查詢:通過執行另外一個SQL映射語句來返回預期的復雜類型 38 SELECT * FROM class WHERE c_id=1; 39 SELECT * FROM teacher WHERE t_id=1 //1 是上一個查詢得到的teacher_id的值 40 --> 41 <select id="getClass2" parameterType="int" resultMap="ClassResultMap2"> 42 select * from class where c_id=#{id} 43 </select> 44 <!-- 使用resultMap映射實體類和字段之間的一一對應關系 --> 45 <resultMap type="me.gacl.domain.Classes" id="ClassResultMap2"> 46 <id property="id" column="c_id"/> 47 <result property="name" column="c_name"/> 48 <association property="teacher" column="teacher_id" select="getTeacher"/> 49 </resultMap> 50 51 <select id="getTeacher" parameterType="int" resultType="me.gacl.domain.Teacher"> 52 SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id} 53 </select>
MyBatis中使用association標簽來解決一對一的關聯查詢,association標簽可用的屬性如下:
- property:對象屬性的名稱
- javaType:對象屬性的類型
- column:所對應的外鍵字段名稱
- select:使用另一個查詢封裝的結果
15.
2.6、MyBatis一對多關聯查詢總結
MyBatis中使用collection標簽來解決一對多的關聯查詢,ofType屬性指定集合中元素的對象類型。
本文部分轉自http://www.cnblogs.com/xdp-gacl/p/4264440.html
16.mybatis調用存儲過程
三、編輯userMapper.xml 編輯userMapper.xml文件,添加如下的配置項 復制代碼 1 <!-- 2 查詢得到男性或女性的數量, 如果傳入的是0就女性否則是男性 3 --> 4 <select id="getUserCount" parameterMap="getUserCountMap" statementType="CALLABLE"> 5 CALL mybatis.ges_user_count(?,?) 6 </select> 7 8 <!-- 9 parameterMap.put("sexid", 0); 10 parameterMap.put("usercount", -1); 11 --> 12 <parameterMap type="java.util.Map" id="getUserCountMap"> 13 <parameter property="sexid" mode="IN" jdbcType="INTEGER"/> 14 <parameter property="usercount" mode="OUT" jdbcType="INTEGER"/> 15 </parameterMap> 復制代碼 四、編寫單元測試代碼 復制代碼 1 package me.gacl.test; 2 3 import java.util.HashMap; 4 import java.util.List; 5 import java.util.Map; 6 7 import me.gacl.custom.model.ConditionUser; 8 import me.gacl.domain.User; 9 import me.gacl.util.MyBatisUtil; 10 import org.apache.ibatis.session.SqlSession; 11 import org.junit.Test; 12 13 /** 14 * @author gacl 15 * 測試調用存儲過程 16 */ 17 public class Test6 { 18 19 @Test 20 public void testGetUserCount(){ 21 SqlSession sqlSession = MyBatisUtil.getSqlSession(); 22 /** 23 * 映射sql的標識字符串, 24 * me.gacl.mapping.userMapper是userMapper.xml文件中mapper標簽的namespace屬性的值, 25 * getUserCount是select標簽的id屬性值,通過select標簽的id屬性值就可以找到要執行的SQL 26 */ 27 String statement = "me.gacl.mapping.userMapper.getUserCount";//映射sql的標識字符串 28 Map<String, Integer> parameterMap = new HashMap<String, Integer>(); 29 parameterMap.put("sexid", 1); 30 parameterMap.put("usercount", -1); 31 sqlSession.selectOne(statement, parameterMap); 32 Integer result = parameterMap.get("usercount"); 33 System.out.println(result); 34 sqlSession.close(); 35 } 36 } 復制代碼