MyBatis
MyBatis是java平臺下一款優秀的持久層框架,它支持定製化 SQL
、存儲過程
以及高級映射
。MyBatis 避免了幾乎所有的 JDBC 代碼
和手動設置參數
以及獲取結果集
。MyBatis 可以使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Ordinary Java Object,簡單普通的 Java對象)映射成數據庫中的記錄。
其前身爲apache的ibatis後來遷移到Gihub並更名爲MyBatis
MyBatis特點:
1.輕量級,自身不依賴其他任何JAR,但需要提供JDBC實現
2.靈活,更加適用於需求變化頻繁的互聯網應用
3.學習成本低,相比ORM框架而言,掌握MyBatis的使用是很輕鬆的
在結構中的位置
可以看出MyBatis處在DAO(數據訪問對象)的位置,處於模型層,用來處理數據庫操作的
回顧一下
DAO的工作職責
:
-
連接數據庫
-
接收輸入數據
-
拼接並執行SQL
-
解析並返回結果
爲什麼需要MyBatis
使用JDBC完成DAO層存在以下問題
-
每次操作都需要手動的創建連接,最後關閉連接
-
對於重複代碼通常開發者都會進行封裝,但是由於每個人的編碼風格不同導致封裝的代碼也沒有固定的套路
-
MyBatis將數據庫連接相關的參數放到配置XML中並封裝了創建連接的代碼
-
-
頻繁的創建和銷燬連接
-
由於數據庫連接使用的是TCP長連接,併發量大的系統中,這樣的方式會導致數據庫連接資源耗盡
-
MyBatis本身實現了連接池,可以解決這一問題,當然後續會更換其他更好的連接池
-
-
接受參數拼接SQL語句並執行
-
每一條SQL語句都是直接寫在代碼中(硬編碼),如果後期需求發生變化,則需要修改源碼中的SQL,然後重新編譯,測試…
-
MyBatis將SQL語句從代碼中剝離到Mapper.xml映射文件中
-
-
解析結果
-
JDBC返回的是ResultSet,必須手動將其映射到一個個的對象中,同樣是重複度很高的代碼;並且存在硬編碼問題
-
MyBatis實現了入參映射到SQL參數,以及結果集映射到POJO對象
-
更多功能
MyBatis在解決上述問題的同時提供了更多實用的功能
- 動態SQL,即在SQL語句中可以包含邏輯處理(判斷,循環等…)
- 高級映射,支持一對一,一對多映射
- 動態代理Mapper,使得可以用面向對象的方式來完成數據庫操作
- 逆向工程,根據表結構自動生成,POJO,Mapper映射和Mapper接口,包含簡單的CRUD
MyBatis構架
-
SqlMapConfig.xml作爲
全局配置
,- 指定MyBatis的基本參數,如運行環境(開發,發佈),事務管理器,數據來源等;
- 以及需要加載的mapper映射文件(從源碼中剝離出來的SQL語句)
-
SqlSessionFactory,負責讀取SqlMapConfig中的參數創建會話
-
SqlSession,通過SqlSessionFactory獲取一個Session(會話)
-
Executor 真正負責執行sql語句的對象
-
MappedStatement用於將輸入參數映射到sql語句,以及結果集映射到POJO
上述構架中,SqlSession以下的部分是MyBatis封裝好的,SqlSession負責調用它們完成操作; 開發過程中不需要涉及(特殊需求除外);
另外SqlSessionFactory和SqlSession也可以通過簡單的代碼獲取到,後續Spring框架能夠自動創建它們
所以使用MyBatis的重點就落在了SqlMapConfig.xml以及Mapper.xml中
CRUD入門
環境搭建
官方文檔:https://mybatis.org/mybatis-3/getting-started.html
1. 創建項目
這裏採用Maven來引入MyBatis,(詳見maven筆記之入土之路)項目採用普通的Java項目,不使用Maven骨架
2. 在pom.xml中添加依賴
<dependencies>
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.17</version>
</dependency>
<dependency>
<groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version>
</dependency>
</dependencies>
3.提供配置文件
3.1MyBatis全局配置
mybatis-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">
<configuration>
<environments default="development">
<!-- 可配置多個環境 並指定默認使用的環境-->
<environment id="development">
<!-- 指定事務管理器-->
<transactionManager type="JDBC"/>
<!-- 指定數據源 就是數據來自哪裏 這裏默認使用MyBatis自帶的連接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- //表示本機 localhost &就是& xml中&需要轉譯-->
<property name="url" value="jdbc:mysql:///mybatisDB?serverTimezone=Asia/Shanghai&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>
</dataSource>
</environment>
</environments>
<!-- 指定要加載的映射文件-->
<mappers>
<mapper resource="mapper/ProductsMapper.xml"/>
</mappers>
</configuration>
3.2 log4日誌模塊,定義輸出格式的配置
log4j.properties
#Global logging configuration
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
這裏先書寫
第一個MyBatis案例根據id查詢
3.3 創建實體類以及對應表
CREATE DATABASE IF NOT EXISTS `mybatisDB` CHARACTER SET utf8;
USE `mybatisDB`;
DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
`pid` int(11) NOT NULL AUTO_INCREMENT,
`pname` varchar(20) DEFAULT NULL,
`price` double DEFAULT NULL,
`pdate` date DEFAULT NULL,
`cid` varchar(20) DEFAULT NULL,
PRIMARY KEY (`pid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
INSERT INTO `products` VALUES (2,'新疆⼤棗',38,NULL,'s002'),(3,'新疆切
糕',68,NULL,'s001'),(4,'⼗三⾹',10,NULL,'s002'),(5,'⽼⼲爹',20,NULL,'s002'),(6,'泰
國咖喱',50.5,'2020-01-02','s001'),(7,'泰國咖喱2',50.5,'2020-01-02','s001');
得到數據庫表
pojo實體類
package com.cx.pojo;
import java.util.Date;
/**
*product實體類
*/
public class Product {
private Integer pid;
private String pname;
private Double price;
private Date pdate;
private String cid;
public Product() {
}
public Product(Integer pid, String pname, Double price, Date pdate, String cid) {
this.pid = pid;
this.pname = pname;
this.price = price;
this.pdate = pdate;
this.cid = cid;
}
public Integer getPid() {
return pid;
}
public void setPid(Integer pid) {
this.pid = pid;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Date getPdate() {
return pdate;
}
public void setPdate(Date pdate) {
this.pdate = pdate;
}
public String getCid() {
return cid;
}
public void setCid(String cid) {
this.cid = cid;
}
@Override
public String toString() {
return "Product{" +
"pid=" + pid +
", pname='" + pname + '\'' +
", price=" + price +
", pdate=" + pdate +
", cid='" + cid + '\'' +
'}';
}
}
3.4創建mapper.xml,直接操作數據庫,編寫sql語句
ProductsMapper.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出現相同的sql時區分不同包-->
<mapper namespace="com.cx.pojo.Product">
<!--查詢語句
id用於表示這條sql
parameterType 表示 sql語句接受一個整數參數
resultType表示 將結果映射到Products對象中
#{} 表示一個站位符等同於 ?
若輸入參數是基礎數據類型則可以隨意寫
若輸入是一個POJO則寫屬性名稱-->
<select id="selectProductBtId" parameterType="int" resultType="com.yyh.pojo.Products">
select *from products where pid = #{pid}
</select>
</mapper>
注意: #{} 表示⼀個站位符等同於 ?,可以用來取值,若輸⼊參數是基礎數據類型則可以隨意寫,若輸⼊是⼀個POJO則寫屬性名稱,但是如何是在字符串中就不能用了,而要用KaTeX parse error: Expected 'EOF', got '#' at position 12: {} 如:pid = #̲{pid} ,pid li…{pid}%”
parameterType 輸入映射 表示 sql語句接受的參數類型,參數在sql語句中直接通過 #{} 或${} 取。
resultType 輸出映射,表示將結果映射到設定的類型中
不要忘記將這個mapper配置到mybatis-config.xml中
<mappers>
<mapper resource="mapper/ProductsMapper.xml"/>
</mappers>
3.4書寫測試代碼
import com.cx.pojo.Product;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
/**
*mybatis入門案例
*/
public class MyTest {
@Test
public void selectById() throws IOException
//1.加載核心配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//2.創建sqlsessionFactoryBuilder對象
SqlSessionFactoryBuilder sqlsessionFactoryBuilder = new SqlSessionFactoryBuilder();
//3.讀取配置文件的流,創建sqlSessionFactory會話 工廠
SqlSessionFactory sqlSessionFactory = sqlsessionFactoryBuilder.build(inputStream);
//4.創建Session對象
SqlSession session = sqlSessionFactory.openSession();
//5.執行查詢語句
Product product = session.selectOne("selectById",3);
System.out.println(product);
session.close();
}
}
點擊test輸出結果:
在這裏,出現了maven點擊test時出現亂碼:如圖
解決辦法見IDEA下使用Maven的test命令亂碼解決方案
那麼第一個案例就書寫完畢。selectOne只能是有兩個參數,一個是sql的id,一個是要傳遞的參數。
多個參數用map
總結一下步驟:
- 在pom.xml中引入相關jar包
- 創建全局配置文件,編寫數據庫參數
- 創建實體類,數據庫對應表
- 編寫對應mapper文件
- 加載核心配置文件mybatis-config.xml
- 創建SqlSessionFactoryBuilder對象,通過.build方法創建會話工廠,讀取流
- sqlSessionFactory.openSession創建sqlSession對象,執行sql語句。
案例2:模糊查詢
在ProductsMapper.xml增加標籤
<select id="selectProductLikeName" parameterType="string" resultType="com.yyh.pojo.Product">
select *from products where pname like "%${pname}%"
</select>
測試代碼:由於多個測試方法都需要工廠所以講工廠作爲屬性並在,@Before中進行初始化,因爲每次都用的
此處不建議session也放在此處,因爲session不是線程安全的對象,所以不建議多個地方使用同一個。
private SqlSessionFactory sqlSessionFactory = null;
@Before
public void before() throws IOException {
//1.加載核心配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//2.創建sqlsessionFactoryBuilder對象
SqlSessionFactoryBuilder sqlsessionFactoryBuilder = new SqlSessionFactoryBuilder();
//3.讀取配置文件的流,創建sqlSessionFactory會話 工廠
sqlSessionFactory = sqlsessionFactoryBuilder.build(inputStream);
}
所以測試代碼:
@Test
public void selectProductLikeName(){
//4.創建Session對象
SqlSession session = sqlSessionFactory.openSession();
//5.執行查詢語句
List<Product> product = session.selectList("selectProductLikeName","新");
System.out.println(product);
session.close();
}
輸出結果:
需要注意的是:在mapper中當需要做將參數進行字符串拼接時則不能在使用#{} 需更換爲${}表示將參數結果提取並拼接到字符串中
注意了:
若MyBatis版本低於3.5.2 則在字符串中使用#{xxx}的方式將導致找不到屬性異常,在低版本中MyBatis將#{xxx}中的xxx當做參數的屬性去參數中查找get方法(很明顯找不到),在字符串內和字符串外獲取屬性的行爲竟然不同,好在3.5.2解決了這個問題
案例3:sql中需要多個參數用map
在ProductsMapper.xml增加標籤
<select id="selectByNameAndPrice" parameterType="map" resultType="com.cx.pojo.Product">
select * from product where pname=#{pname} and price = #{price}
</select>
書寫測試代碼
@Test
public void selectByNameAndPrice(){
//4.創建Session對象
SqlSession session = sqlSessionFactory.openSession();
Map<String,Object> map = new HashMap<>();
map.put("pname","⼗三⾹");
map.put("price",10);
//5.執行查詢語句
Product product = session.selectOne("selectByNameAndPrice",map);
System.out.println(product);
session.close();
}
運行結果
案例4:插入數據
在ProductsMapper.xml增加標籤
<insert id="insertProduct" parameterType="com.cx.pojo.Product">
insert into product values (null,#{pname},#{price},#{pdate},#{cid})
</insert>
書寫測試代碼
@Test
public void insertProduct(){
//設置自動提交爲true 默認爲false
//4.創建Session對象
SqlSession session = sqlSessionFactory.openSession();
Product product = new Product();
product.setPrice(100d);
product.setPname("糧食2789");
product.setPdate(new Date());
product.setCid("2222");
//5.執行插入語
int o = session.insert("insertProduct", product);
session.commit();//手動提交
System.out.println(o);
session.close();
}
運行結果:
強調:
當sql爲update insert delete時需要commit纔會生效,SqlSession默認不自動提交的,可以在獲取SqlSession時指定自動提交,也可以在執行完後手動調用commit;
sql爲update insert delete時返回值爲受影響行數
獲取插入數據的id
很多情況下需要獲取剛剛添加的記錄的id用來做表關聯,要獲得id有兩種方式
4.1對於支持自增的數據庫MySQL,下述案例:MyBatis會把id存儲到傳入對象的pid屬性中
<insert id="insertProduct" parameterType="com.cx.pojo.Product" keyProperty="pid" useGeneratedKeys="true">
insert into product values (null,#{pname},#{price},#{pdate},#{cid})
</insert>
4.2兼容不支持自增的數據庫
<insert id="insertProduct" parameterType="com.cx.pojo.Product">
insert into product values (null,#{pname},#{price},#{pdate},#{cid})
<!--指定如何獲取id 並放入對象某個屬性中 -->
<selectKey resultType="int" keyProperty="pid" order="AFTER" >
select last_insert_id();
</selectKey>
</insert>
注意:select last_insert_id();是mysql的函數,oracle沒有,那就需要將其替換爲oracle中生產id的函數,selectKey的原理是執行這個sql函數,然後將結果放入對象的屬性中,order指定id放入對象屬性是在執行sql前或者後
關於before和after的選擇,要根據id的來源是自增的還是自己編寫語句獲取的,如下:
案例5:更新數據
在ProductsMapper.xml增加標籤
<update id="updateProduct" parameterType="com.cx.pojo.Product">
update product set
pname=#{pname},
price=#{price},
pdate=#{pdate},
cid=#{cid}
where pid=#{pid}
</update>
書寫測試代碼
@Test
public void updateProduct(){
//設置自動提交爲true 默認爲false
//4.創建Session對象
SqlSession session = sqlSessionFactory.openSession();
Product product = session.selectOne("selectById", 3);
product.setPrice(10d);
product.setPname("哈密瓜");
product.setPdate(new Date());
product.setCid("s10022");
//5.執行更新語
int o = session.update("updateProduct", product);
session.commit();//手動提交
System.out.println(o);
session.close();
}
運行結果:
案例6:刪除案例
在ProductsMapper.xml增加標籤
<delete id="deleteById" parameterType="int">
delete from product where pid = #{pid}
</delete>
書寫測試代碼
@Test
public void deleteProduct(){
//4.創建Session對象
SqlSession session = sqlSessionFactory.openSession();
//5.執行刪除語
int o = session.delete("deleteById", 3);
session.commit();//手動提交
System.out.println(o);
session.close();
}
下一篇:MyBatis進階----進階篇