Mybatis簡單入門
1、Mybatis概述
MyBatis 是支援普通 SQL 查詢(相比較於Hibernate的封裝,Mybatis是半自動化的JDBC封裝,一個特點就是Mybatis執行的SQL查詢語句需要自己在配置檔案中寫),儲存過程和高階對映的優秀持久層框架。MyBatis 消除了幾乎所有的 JDBC 程式碼和引數的手工設定以及對結果集的檢索。MyBatis 可以使用簡單的XML 或註解用於配置和原始對映,將介面和 Java 的 POJO(Plain Old Java Objects,普通的Java 物件)對映成資料庫中的記錄。
2、Mybatis原理解析
下面以Mybatis簡單的執行流程
1、載入mybatis全域性配置檔案(資料來源、mapper對映檔案等),解析配置檔案,MyBatis基於XML配置檔案生成Configuration,和一個個MappedStatement(包括了引數對映配置、動態SQL語句、結果對映配置),其對應著<select | update | delete | insert>標籤項。
2、SqlSessionFactoryBuilder通過Configuration物件生成SqlSessionFactory,用來開啟SqlSession。
3、SqlSession物件完成和資料庫的互動:
a、使用者程式呼叫mybatis介面層api(即Mapper介面中的方法)
b、SqlSession通過呼叫api的Statement ID找到對應的MappedStatement物件
c、通過Executor(負責動態SQL的生成和查詢快取的維護)將MappedStatement物件進行解析,sql引數轉化、動態sql拼接,生成jdbc Statement物件
d、JDBC執行sql。
e、藉助MappedStatement中的結果對映關係,將返回結果轉化成HashMap、JavaBean等儲存結構並返回。
下面是Mybatis的框架原理圖
3、Mybatis簡單例項
(1)匯入相關jar包以及Mybatis執行環境核心jar包和連線資料庫的包
(2)建立一張簡單的資料表
(3)建立Java物件(PO型別)
1 package cn.mybatis.po; 2 3 public class User { 4private int id; 5private String username; 6private String password; 7private String address; 8private String sex; 9 10public int getId() { 11return id; 12} 13 14public String getUsername() { 15return username; 16} 17 18public String getPassword() { 19return password; 20} 21 22public String getAddress() { 23return address; 24} 25 26public String getSex() { 27return sex; 28} 29 30public void setId(int id) { 31this.id = id; 32} 33 34public void setUsername(String username) { 35this.username = username; 36} 37 38public void setPassword(String password) { 39this.password = password; 40} 41 42public void setAddress(String address) { 43this.address = address; 44} 45 46public void setSex(String sex) { 47this.sex = sex; 48} 49 50@Override 51public String toString() { 52return "User{" + 53"id=" + id + 54", username='" + username + '\'' + 55", password='" + password + '\'' + 56", address='" + address + '\'' + 57", sex='" + sex + '\'' + 58'}'; 59} 60 } User實體類
(4)建立Mybatis核心配置檔案(SqlMapConfig.xml)
在核心配置檔案配置連線資料庫的相關資訊,(如果是和Spring整合,則可以放在Spring配置檔案中進行對資料庫的配置)
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration 3PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 4"http://mybatis.org/dtd/mybatis-3-config.dtd"> 5 <configuration> 6<!--載入資原始檔--> 7 <!--<properties resource="jdbc.properties"></properties> 8<!–settings配置LOG4J輸出日誌 –> 9<settings> 10<setting name="logImpl" value="LOG4J"/> 11</settings>--> 12<!--typeAliases配置包的別名--> 13 14<!--environments配置了資料庫連線,配置了driver、url、username、password屬性--> 15<environments default="development"> 16<environment id="development"> 17<transactionManager type="JDBC"> 18<!--<property name="" value="" />--> 19</transactionManager> 20<dataSource type="POOLED"> 21<property name="driver" value="com.mysql.jdbc.Driver" /> 22<property name="url" value="jdbc:mysql:///mybatis01" /> 23<property name="username" value="root" /> 24<property name="password" value="123" /> 25</dataSource> 26</environment> 27</environments> 28<!--配置一個SQL語句和對映的配置檔案--> 29<mappers> 30<mapper resource="UserMapper.xml" /> 31</mappers> 32 </configuration> Mybatis核心配置檔案
(5)建立一個Mapper.xml檔案,對應編寫所需要的Sql查詢操作
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <!--mapper為根元素,namespace指定了名稱空間--> 6 <mapper namespace="test"> 7<!--定義一個SELECT查詢--> 8<!--parameterType:指定輸入引數的型別--> 9<!--#{}表示佔位符--> 10<!--#{id}:其中的id表示的就是接受的輸入引數, 11引數名稱就是id, 12這裡指出:如果輸入引數是簡單型別,#{}中的引數名可以任意設定(value或者其他名稱)--> 13<!--resultType:指定輸出型別(即指定輸出結果所對映的Java物件型別)--> 14<select id="findUserById" parameterType="int" resultType="cn.mybatis.po.User"> 15SELECT * FROM t_user WHERE id = #{id} 16</select> 17 </mapper> UserMapper配置檔案
(7)建立測試程式,對剛剛編寫的select查詢進行測試
1 package cn.mybatis.first; 2 3 import cn.mybatis.po.User; 4 import org.apache.ibatis.io.Resources; 5 import org.apache.ibatis.session.SqlSession; 6 import org.apache.ibatis.session.SqlSessionFactory; 7 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 8 9 import java.io.IOException; 10 import java.io.InputStream; 11 12 public class Test { 13 14public User findUserById() throws IOException { 15//得到mybatis配置檔案 16String resource = "SqlMapConfig.xml"; 17//得到配置檔案的檔案流資訊 18InputStream inputStream = Resources.getResourceAsStream(resource); 19//建立會話工廠 傳入mybatis的配置檔案資訊 20SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 21//通過會話工廠得到SqlSession 22SqlSession sqlSession = sqlSessionFactory.openSession(); 23//通過sqlSession來操作資料庫 24//第一個引數就是對映檔案中statment的id:namespace +statment的id 25//第二個引數就是制定對映檔案中的parameterType型別的引數 26User user = sqlSession.selectOne("test.findUserById",1); 27//System.out.println(user); 28 29//釋放會話資源 30 31try { 32sqlSession.close(); 33} catch (Exception e) { 34e.printStackTrace(); 35} 36return user; 37} 38 39public static void main(String[] args) { 40// TODO Auto-generated method stub 41Test test = new Test(); 42 43try { 44System.out.println(test.findUserById()); 45} catch (IOException e) { 46e.printStackTrace(); 47} 48 49} 50 } Test測試程式
(8)加入Log4j日誌檔案
1 ### direct log messages to stdout ### 2 3 log4j.rootLogger=DEBUG, stdout 4 5 log4j.appender.stdout=org.apache.log4j.ConsoleAppender 6 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 7 log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n Log4j.properties
(9)測試結果
4.其他CRUD操作
(1)insert操作
在Mapper檔案中新增響應的SQL配置,以及使用MySQL中的LAST_INSERT_ID()函式得到增加的資料的主鍵值
<insert id="addUser" parameterType="cn.mybatis.po.User"> <!-- 現在需要得到剛剛插入的記錄中的主鍵值,只適用於自增主鍵的情況 LAST_INSERT_ID() keyProperty:將查詢到的主鍵值設定到parameterType指定物件中的那個屬性 order:指定相對於insert的順序 resultType:指定對映結果的結果型別 --> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> SELECT LAST_INSERT_ID() </selectKey> INSERT INTO t_user(id,username,password,address,sex) VALUES(#{id},#{username},#{password},#{address}, #{sex}); </insert>
插入資料的日誌資訊,沒有使用sqlSession.commit();之前的日誌情況
從上面的圖中可以看出,沒有新增commit的時候,事務進行了回滾,所以要想新增資料,需要自己手動提交(在沒有整合Spring之前)
附上insertUser的函式
1public void inserUser()throws IOException { 2//得到配置檔案的檔案流資訊 3InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); 4SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 5SqlSession sqlSession = sqlSessionFactory.openSession(); 6 7User user = new User("World","1234","武漢市","男"); 8sqlSession.insert("test.addUser",user); 9System.out.println(user.getId()); 10sqlSession.commit(); 11//釋放會話資源 12try { 13sqlSession.close(); 14} catch (Exception e) { 15e.printStackTrace(); 16} 17} inserUser函式
(2)模糊查詢
首先配置Mapper檔案,${}和#{}的簡單區別如下:
1<!-- 2模糊查詢可能會查詢多條記錄 3resultType:指定的就是查詢結果對應的單條記錄型別 4${}:表示將輸入的引數不加任何的修飾,直接作為字串拼接在SQL中 5但是這樣直接拼接,容易導致SQL注入的隱患 6${value}中的value表示接受的輸入引數,注意如果輸入引數是簡單型別,其中的形參只能用value 7--> 8<select id="findUserByUsername" parameterType="java.lang.String" resultType="cn.mybatis.po.User"> 9SELECT * FROM t_user WHERE username LIKE '%${value}%' 10</select>
使用查詢的時候碰到一個小錯誤,由於之前測試的insert方法,其中在User實體類中添加了有參建構函式,所以出現了下面的錯誤,分析原因就是:使用Mybatis查詢的時候需要在實體類中加入無參構造方法(當然如果實體類本身沒有建構函式,就會是預設的無參建構函式)
附上findByUsername的函式實現
1public void findUserByUsername()throws IOException { 2//得到配置檔案的檔案流資訊 3InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); 4SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 5SqlSession sqlSession = sqlSessionFactory.openSession(); 6 7List<User> userList = sqlSession.selectList("test.findUserByUsername","u"); 8System.out.println(userList); 9//釋放會話資源 10try { 11sqlSession.close(); 12} catch (Exception e) { 13e.printStackTrace(); 14} 15} findByUsername函式實現
(3)刪除操作
首先在Mapper中配置刪除的操作
1<delete id="deleteUser" parameterType="java.lang.Integer"> 2DELETE FROM t_user WHERE id = #{value} 3</delete>
執行測試程式,同insert中一樣,需要手動提交事務,如下面所示
最終結果:
資料表中刪除了編號為10的資料記錄
5.細節整理
(1)關於示例程式中一些相關類的理解
a)SqlSessionFactoryBuilder
用來建立SqlSessionFactory。因為SqlSessionFactory使用了單例模式,所以不需要使用單例模式來管理SqlSessionFactoryBuilder,只需要在建立SqlSessionFactory時候使用一次就可以
b)SqlSessionFactory
會話工廠,用來建立SqlSession。可以使用單例模式來管理SqlSessionFactory這個會話工廠,工廠建立之後,就一直使用一個例項。
c)SqlSession
面向程式設計師的介面,提供了操作資料庫的方法。SqlSession是執行緒不安全的(原因:在SqlSession實現類中除了介面中的操作資料庫的方法之外,還有資料域的屬性,比如說一些提交的資料等等,所以在多執行緒併發請求的時候,會導致執行緒不安全),所以我們可以將SqlSession使用在方法體裡面,這樣每個執行緒都有自己的方法,就不會衝突
(2)Mybatis中mapper對映檔案
如同解釋Mybatis執行原理的時候一樣,Mapper對映檔案中配置的Sql語句,實際上在執行的時候都被封裝稱為一個個MapperStatment物件,即Mapper對映檔案是按照statment來管理不同的Sql。在編寫程式的時候,我們在使用SqlSession其中的操作資料庫的方法(selectOne,selectList等等)的時候,傳入的引數除了實參(id,模糊查詢的字串等等)之外,還需要傳入的就是相應的Sql位置,而Sql是被Statment管理,所以就是傳入namespace+statmentId
(3)佔位符
#{id}:其中的id表示的就是接受的輸入引數, 引數名稱就是id, 這裡指出:如果輸入引數是簡單型別,#{}中的引數名可以任意設定(value或者其他名稱)
${value}:表示將輸入的引數不加任何的修飾,直接作為字串拼接在SQL中但是這樣直接拼接,容易導致SQL注入的隱患${value}中的value表示接受的輸入引數,注意如果輸入引數是簡單型別,其中的形參只能用value
(4)別名定義
①單個別名的定義
<typeAliases> <!--針對單個別名的定義--> <typeAlias type="cn.mybatis.po.User" alias="user"></typeAlias> </typeAliases>
定義別名後的使用
<select id="findUserByIdTest" parameterType="int" resultType="user"> SELECT * FROM t_user WHERE id = #{id} </select>
②批量別名的定義
<typeAliases> <!--批量別名定義:Mybatis在定義別名的時候會自動掃描包中的po類,自動的將別名定義為類名(首字母大寫或者小寫都可以)--> <package name="cn.mybatis.po"></package> </typeAliases>
(5)在SqlMapConfig.xml中載入Mapper對映檔案的時候,除了通過resource的方式,還可以使用mapper介面載入的方式來實現
①首先先注意一點:
在配置mybatis-config.xml時,其中的節點是有順序的,配置順序依次為:
properties/settings/typeAliases/typeHandlers/objectFactory/objectWrapperFactory/plugins/environments/databaseIdProvider/mappers
②使用mapper載入的方式,要將mapper介面和mapper配置檔案放在同一目錄下面,並且檔名稱一致,而且要遵循mapper代理的方式進行開發
<mappers> <mapper class="cn.mybatis.mapper.UserMapper"></mapper> </mappers>
6.Mybatis開發dao方法簡介
(1)使用dao介面+實現類的方式
a)首先編寫介面,如同一般編寫模式方式進行編寫
1 package cn.mybatis.dao; 2 3 import cn.mybatis.po.User; 4 5 /** 6* 原始Dao方式開發:dao介面+dao實現類的方式 7*/ 8 public interface UserDao { 9 10//根據id查詢資訊 11public User findUserById(int id) throws Exception; 12//新增資訊 13public void insertUser(User user) throws Exception; 14//刪除資訊 15public void deleteUser(int id) throws Exception; 16 } dao介面
b)然後編寫介面實現
1 package cn.mybatis.dao.daoImpl; 2 3 import cn.mybatis.dao.UserDao; 4 import cn.mybatis.po.User; 5 import org.apache.ibatis.session.SqlSession; 6 import org.apache.ibatis.session.SqlSessionFactory; 7 import org.junit.Test; 8 9 public class UserDaoImpl implements UserDao { 10 11//使用構造方法注入SqlSessionFactory 12private SqlSessionFactory sqlSessionFactory; 13 14public UserDaoImpl(SqlSessionFactory sqlSessionFactory) { 15this.sqlSessionFactory = sqlSessionFactory; 16} 17 18@Override 19@Test 20public User findUserById(int id) throws Exception { 21SqlSession sqlSession = sqlSessionFactory.openSession(); 22 23User user = sqlSession.selectOne("test.findUserById",id); 24 25sqlSession.close(); 26return user; 27} 28 29@Override 30public void insertUser(User user) throws Exception { 31SqlSession sqlSession = sqlSessionFactory.openSession(); 32 //User user1 = new User("test1","123","洪山區","男"); 33sqlSession.insert("test.findUserById",user); 34sqlSession.commit(); 35sqlSession.close(); 36} 37 38@Override 39public void deleteUser(int id) throws Exception { 40SqlSession sqlSession = sqlSessionFactory.openSession(); 41 42sqlSession.delete("test.findUserById",id); 43sqlSession.commit(); 44sqlSession.close(); 45} 46 } dao介面實現類
c)Mapper配置檔案和SqlConfig配置檔案不變
d)使用Junit進行測試
1 package cn.mybatis.testdao; 2 3 import cn.mybatis.dao.UserDao; 4 import cn.mybatis.dao.daoImpl.UserDaoImpl; 5 import cn.mybatis.po.User; 6 import org.apache.ibatis.io.Resources; 7 import org.apache.ibatis.session.SqlSessionFactory; 8 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 9 import org.junit.After; 10 import org.junit.Before; 11 import org.junit.Test; 12 13 import java.io.InputStream; 14 15 public class UserDaoImplTest { 16 17private SqlSessionFactory sqlSessionFactory; 18 19@Before 20public void setUp() throws Exception { 21InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); 22sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 23} 24 25@Test 26public void testFindUserById() throws Exception{ 27//建立UserDao的物件 28UserDao userDao = new UserDaoImpl(sqlSessionFactory); 29 30//呼叫UserDao的方法 31User user = userDao.findUserById(1); 32 33System.out.println(user ); 34} 35 } Junit測試
e)測試結果
f)原始dao方法的問題
①dao介面實現中存在大量的模板方法(即很多重複性的程式碼 )
②呼叫SqlSession方法的時候將statmentid硬編碼了
③條用SqlSession方法的時候傳入的引數,由於使用泛型,所以在編譯階段不會報錯(即使傳入引數錯誤)
(2)使用Mapper代理的方法(即只需要Mapper介面)
(a)使用mapper方式的規範
①在使用mapper代理的方式中,namespace的值應該是mapper介面的路徑
②在mapper.java介面檔案中的介面方法名稱和mapper.xml中的statment的id一致
③在mapper.java介面檔案中的介面方法的輸入引數和mapper.xml中的statment的parameterType一致
④在mapper.java介面檔案中的介面方法的返回值型別和mapper.xml中的statment的resultType一致
(b)查詢、刪除操作例項
①編寫mapper.xml配置檔案,其中包含select和delete的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代理的方式中,namespace的值應該是mapper介面的路徑--> <mapper namespace="cn.mybatis.mapper.UserMapper"> <select id="findUserById" parameterType="int" resultType="cn.mybatis.po.User"> SELECT * FROM t_user WHERE id = #{id} </select> <delete id="deleteUser" parameterType="java.lang.Integer"> DELETE FROM t_user WHERE id = #{value} </delete> </mapper> mapper.xml配置檔案
②編寫mapper介面,按照mapper代理的方式開發規範來編寫mapper的介面
1 package cn.mybatis.testmapper; 2 3 import cn.mybatis.mapper.UserMapper; 4 import cn.mybatis.po.User; 5 import org.apache.ibatis.io.Resources; 6 import org.apache.ibatis.session.SqlSession; 7 import org.apache.ibatis.session.SqlSessionFactory; 8 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 9 import org.junit.After; 10 import org.junit.Before; 11 import org.junit.Test; 12 13 import java.io.InputStream; 14 15 16 public class UserMapperTest { 17 18private SqlSessionFactory sqlSessionFactory; 19 20@Before 21public void setUp() throws Exception { 22InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); 23sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 24} 25 26@Test 27public void testFindUserById() throws Exception{ 28 29SqlSession sqlSession = sqlSessionFactory.openSession(); 30//得到UserMapper的代理物件 31UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 32 33User user = userMapper.findUserById(9); 34 35System.out.println(user); 36} 37 38@Test 39public void testDeleteUser() throws Exception { 40SqlSession sqlSession = sqlSessionFactory.openSession(); 41UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 42 43userMapper.deleteUser(9); 44sqlSession.commit(); 45} 46 47@After 48public void tearDown() throws Exception { 49} 50 } mapper介面
③Junit測試
1 package cn.mybatis.testmapper; 2 3 import cn.mybatis.mapper.UserMapper; 4 import cn.mybatis.po.User; 5 import org.apache.ibatis.io.Resources; 6 import org.apache.ibatis.session.SqlSession; 7 import org.apache.ibatis.session.SqlSessionFactory; 8 import org.apache.ibatis.session.SqlSessionFactoryBuilder; 9 import org.junit.After; 10 import org.junit.Before; 11 import org.junit.Test; 12 13 import java.io.InputStream; 14 15 16 public class UserMapperTest { 17 18private SqlSessionFactory sqlSessionFactory; 19 20@Before 21public void setUp() throws Exception { 22InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); 23sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 24} 25 26@Test 27public void testFindUserById() throws Exception{ 28 29SqlSession sqlSession = sqlSessionFactory.openSession(); 30//得到UserMapper的代理物件 31UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 32 33User user = userMapper.findUserById(8); 34 35System.out.println(user); 36} 37 38@Test 39public void testDeleteUser() throws Exception { 40SqlSession sqlSession = sqlSessionFactory.openSession(); 41UserMapper userMapper = sqlSession.getMapper(UserMapper.class); 42 43userMapper.deleteUser(8); 44} 45 46@After 47public void tearDown() throws Exception { 48} 49 } Junit測試
④查詢結果展示
⑤刪除結果展示