原生缺陷:
? ? ? ?數據庫dao層操作缺陷:
? ? ? ? ? ? ? ?①jdbc的增刪改查代碼的冗余過大,查詢的時候需要遍歷。
? ? ? ? ? ? ? ?②Sql語句和數據庫相關參數和代碼的耦合性過高。
? ? ? ? ? ? ? ?解決:使用Mybatis
? ? ? ?業務層缺陷:
? ? ? ? ? ? ? ?①業務層和數據庫層的耦合性過高。
? ? ? ? ? ? ? ?②業務復雜,一個業務方法中的數據庫操作要在一個事務管理內。
? ? ? ? ? ? ? ?解決:使用Spring ? ? ? ?
? ? ? ?控制層缺陷:
? ? ? ? ? ? ? ?①請求數據的獲取以及強轉
? ? ? ? ? ? ? ?②請求數據封裝對象
? ? ? ? ? ? ? ?解決:使用SpringMVC ? ? ? ?
本文講述mybatis? ? ? ? ? ? ? ?
什么是 MyBatis?
MyBatis 是一款優秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)為數據庫中的記錄。
安裝
要使用 MyBatis, 只需將?mybatis-x.x.x.jar?文件置于 classpath 中即可。
如果使用 Maven 來構建項目,則需將下面的 dependency 代碼置于 pom.xml 文件中:
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>x.x.x</version>
</dependency>
從 XML 中構建 SqlSessionFactory
每個基于 MyBatis 的應用都是以一個 SqlSessionFactory 的實例為核心的。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或一個預先定制的 Configuration 的實例構建出 SqlSessionFactory 的實例。
從 XML 文件中構建 SqlSessionFactory 的實例非常簡單,建議使用類路徑下的資源文件進行配置。 但是也可以使用任意的輸入流(InputStream)實例,包括字符串形式的文件路徑或者 file:// 的 URL 形式的文件路徑來配置。MyBatis 包含一個名叫 Resources 的工具類,它包含一些實用方法,可使從 classpath 或其他位置加載資源文件更加容易。
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
XML 配置文件中包含了對 MyBatis 系統的核心設置,包含獲取數據庫連接實例的數據源(DataSource)和決定事務作用域和控制方式的事務管理器(TransactionManager)。 XML 配置文件的詳細內容后面再探討,這里先給出一個簡單的示例:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//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>
當然,還有很多可以在 XML 文件中進行配置,上面的示例指出的則是最關鍵的部分。 要注意 XML 頭部的聲明,它用來驗證 XML 文檔正確性。environment 元素體中包含了事務管理和連接池的配置。mappers 元素則是包含一組映射器(mapper),這些映射器的 XML 映射文件包含了 SQL 代碼和映射定義信息。
核心組件
生命周期
SqlSessionFactoryBuilder
這個類可以被實例化、使用和丟棄,一旦創建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域(也就是局部方法變量)。 你可以重用 SqlSessionFactoryBuilder 來創建多個 SqlSessionFactory 實例,但是最好還是不要讓其一直存在,以保證所有的 XML 解析資源可以被釋放給更重要的事情。
SqlSessionFactory 一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建另一個實例。 使用 SqlSessionFactory 的最佳實踐是在應用運行期間不要重復創建多次,多次重建 SqlSessionFactory 被視為一種代碼“壞味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是應用作用域。 有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式。
每個線程都應該有它自己的 SqlSession 實例。SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。 絕對不能將 SqlSession 實例的引用放在一個類的靜態域,甚至一個類的實例變量也不行。 也絕不能將 SqlSession 實例的引用放在任何類型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你現在正在使用一種 Web 框架,要考慮 SqlSession 放在一個和 HTTP 請求對象相似的作用域中。 換句話說,每次收到的 HTTP 請求,就可以打開一個 SqlSession,返回一個響應,就關閉它。 這個關閉操作是很重要的,你應該把這個關閉操作放到 finally 塊中以確保每次都能執行關閉。 下面的示例就是一個確保 SqlSession 關閉的標準模式:
try (SqlSession session = sqlSessionFactory.openSession()) {// 你的應用邏輯代碼 }
在你的所有的代碼中一致地使用這種模式來保證所有數據庫資源都能被正確地關閉。
SpringBoot-mabatis實現用戶增刪改查
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.0.1</version></dependency>
mysql和mybatis在springboot中的常用配置介紹
# DataSourceProperties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root#賬號
spring.datasource.password=ytywan1314#密碼
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=15#最大連接數
spring.datasource.hikari.minimum-idle=5#最小空閑
spring.datasource.hikari.idle-timeout=30000#超時時間# MybatisProperties
mybatis.mapper-locations=classpath:mapper/*.xml#映射文件的路徑
mybatis.type-aliases-package=com.now.community.community.entity#實體類所在包
mybatis.configuration.useGeneratedKeys=true#自動主鍵
mybatis.configuration.mapUnderscoreToCamelCase=true#實體類和表屬性命名自動對應,駝峰對應下劃線
我們寫一下實體類:
package com.now.community.community.entity;import java.util.Date;public class User {private int id;private String username;private String password;private String salt;private String email;private int type;private int status;private String activationCode;private String headerUrl;private Date createTime;@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +", salt='" + salt + '\'' +", email='" + email + '\'' +", type=" + type +", status=" + status +", activationCode='" + activationCode + '\'' +", headerUrl='" + headerUrl + '\'' +", createTime=" + createTime +'}';}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getSalt() {return salt;}public void setSalt(String salt) {this.salt = salt;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public int getType() {return type;}public void setType(int type) {this.type = type;}public int getStatus() {return status;}public void setStatus(int status) {this.status = status;}public String getActivationCode() {return activationCode;}public void setActivationCode(String activationCode) {this.activationCode = activationCode;}public String getHeaderUrl() {return headerUrl;}public void setHeaderUrl(String headerUrl) {this.headerUrl = headerUrl;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}
}
對應的表
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(50) DEFAULT NULL,`password` varchar(50) DEFAULT NULL,`salt` varchar(50) DEFAULT NULL,`email` varchar(100) DEFAULT NULL,`type` int(11) DEFAULT NULL COMMENT '0-普通用戶; 1-超級管理員; 2-版主;',`status` int(11) DEFAULT NULL COMMENT '0-未激活; 1-已激活;',`activation_code` varchar(100) DEFAULT NULL,`header_url` varchar(200) DEFAULT NULL,`create_time` timestamp NULL DEFAULT NULL,PRIMARY KEY (`id`),KEY `index_username` (`username`(20)),KEY `index_email` (`email`(20))
) ENGINE=InnoDB AUTO_INCREMENT=164 DEFAULT CHARSET=utf8
我們寫一個dao層的bean
@Mapper
public interface UserMapper {User selectById(int id);User selectByName(String useName);User selectByEmail(String email);int insertUser(User user);int updateStatus(int id,int status);int updateHeader(int id,String headerUrl);int updatePassword(int id,String password);}
XML 映射文件
MyBatis 的真正強大在于它的映射語句,這是它的魔力所在。由于它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼。MyBatis 為聚焦于 SQL 而構建,以盡可能地為你減少麻煩。
SQL 映射文件只有很少的幾個頂級元素(按照應被定義的順序列出):
- cache?– 對給定命名空間的緩存配置。
- cache-ref?– 對其他命名空間緩存配置的引用。
- resultMap?– 是最復雜也是最強大的元素,用來描述如何從數據庫結果集中來加載對象。
parameterMap?– 已被廢棄!老式風格的參數映射。更好的辦法是使用內聯參數,此元素可能在將來被移除。文檔中不會介紹此元素。- sql?– 可被其他語句引用的可重用語句塊。
- insert?– 映射插入語句
- update?– 映射更新語句
- delete?– 映射刪除語句
- select?– 映射查詢語句
select
查詢語句是 MyBatis 中最常用的元素之一,光能把數據存到數據庫中價值并不大,只有還能重新取出來才有用,多數應用也都是查詢比修改要頻繁。對每個插入、更新或刪除操作,通常間隔多個查詢操作。這是 MyBatis 的基本原則之一,也是將焦點和努力放在查詢和結果映射的原因。簡單查詢的 select 元素是非常簡單的。比如:
<select id="selectPerson" parameterType="int" resultType="hashmap">SELECT * FROM PERSON WHERE ID = #{id} </select>
這個語句被稱作 selectPerson,接受一個 int(或 Integer)類型的參數,并返回一個 HashMap 類型的對象,其中的鍵是列名,值便是結果行中的對應值。
注意參數符號:
#{id}
這就告訴 MyBatis 創建一個預處理語句(PreparedStatement)參數,在 JDBC 中,這樣的一個參數在 SQL 中會由一個“?”來標識,并被傳遞到一個新的預處理語句中,就像這樣:
// 近似的 JDBC 代碼,非 MyBatis 代碼... String selectPerson = "SELECT * FROM PERSON WHERE ID=?"; PreparedStatement ps = conn.prepareStatement(selectPerson); ps.setInt(1,id);
當然,使用 JDBC 意味著需要更多的代碼來提取結果并將它們映射到對象實例中,而這就是 MyBatis 節省你時間的地方。參數和結果映射還有更深入的細節。這些細節會分別在后面單獨的小節中呈現。
select 元素允許你配置很多屬性來配置每條語句的作用細節。
<selectid="selectPerson"parameterType="int"parameterMap="deprecated"resultType="hashmap"resultMap="personResultMap"flushCache="false"useCache="true"timeout="10"fetchSize="256"statementType="PREPARED"resultSetType="FORWARD_ONLY">
屬性 | 描述 |
---|---|
id | 在命名空間中唯一的標識符,可以被用來引用這條語句。 |
parameterType | 將會傳入這條語句的參數類的完全限定名或別名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器(TypeHandler) 推斷出具體傳入語句的參數,默認值為未設置(unset)。 |
resultType | 從這條語句中返回的期望類型的類的完全限定名或別名。 注意如果返回的是集合,那應該設置為集合包含的類型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同時使用。 |
resultMap | 外部 resultMap 的命名引用。結果集的映射是 MyBatis 最強大的特性,如果你對其理解透徹,許多復雜映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同時使用。 |
flushCache | 將其設置為 true 后,只要語句被調用,都會導致本地緩存和二級緩存被清空,默認值:false。 |
useCache | 將其設置為 true 后,將會導致本條語句的結果被二級緩存緩存起來,默認值:對 select 元素為 true。 |
timeout | 這個設置是在拋出異常之前,驅動程序等待數據庫返回請求結果的秒數。默認值為未設置(unset)(依賴驅動)。 |
fetchSize | 這是一個給驅動的提示,嘗試讓驅動程序每次批量返回的結果行數和這個設置值相等。 默認值為未設置(unset)(依賴驅動)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 中的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED。 |
resultSetType | FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等價于 unset) 中的一個,默認值為 unset (依賴驅動)。 |
databaseId | 如果配置了數據庫廠商標識(databaseIdProvider),MyBatis 會加載所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
resultOrdered | 這個設置僅針對嵌套結果 select 語句適用:如果為 true,就是假設包含了嵌套結果集或是分組,這樣的話當返回一個主結果行的時候,就不會發生有對前面結果集的引用的情況。 這就使得在獲取嵌套的結果集的時候不至于導致內存不夠用。默認值:false。 |
resultSets | 這個設置僅對多結果集的情況適用。它將列出語句執行后返回的結果集并給每個結果集一個名稱,名稱是逗號分隔的。 |
insert, update 和 delete
數據變更語句 insert,update 和 delete 的實現非常接近:
<insertid="insertAuthor"parameterType="domain.blog.Author"flushCache="true"statementType="PREPARED"keyProperty=""keyColumn=""useGeneratedKeys=""timeout="20"><updateid="updateAuthor"parameterType="domain.blog.Author"flushCache="true"statementType="PREPARED"timeout="20"><deleteid="deleteAuthor"parameterType="domain.blog.Author"flushCache="true"statementType="PREPARED"timeout="20">
屬性 | 描述 |
---|---|
id | 命名空間中的唯一標識符,可被用來代表這條語句。 |
parameterType | 將要傳入語句的參數的完全限定類名或別名。這個屬性是可選的,因為 MyBatis 可以通過類型處理器推斷出具體傳入語句的參數,默認值為未設置(unset)。 |
flushCache | 將其設置為 true 后,只要語句被調用,都會導致本地緩存和二級緩存被清空,默認值:true(對于 insert、update 和 delete 語句)。 |
timeout | 這個設置是在拋出異常之前,驅動程序等待數據庫返回請求結果的秒數。默認值為未設置(unset)(依賴驅動)。 |
statementType | STATEMENT,PREPARED 或 CALLABLE 的一個。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED。 |
useGeneratedKeys | (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數據庫內部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關系數據庫管理系統的自動遞增字段),默認值:false。 |
keyProperty | (僅對 insert 和 update 有用)唯一標記一個屬性,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設置它的鍵值,默認值:未設置(unset)。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。 |
keyColumn | (僅對 insert 和 update 有用)通過生成的鍵值設置表中的列名,這個設置僅在某些數據庫(像 PostgreSQL)是必須的,當主鍵列不是表中的第一列的時候需要設置。如果希望使用多個生成的列,也可以設置為逗號分隔的屬性名稱列表。 |
databaseId | 如果配置了數據庫廠商標識(databaseIdProvider),MyBatis 會加載所有的不帶 databaseId 或匹配當前 databaseId 的語句;如果帶或者不帶的語句都有,則不帶的會被忽略。 |
sql
這個元素可以被用來定義可重用的 SQL 代碼段,這些 SQL 代碼可以被包含在其他語句中。它可以(在加載的時候)被靜態地設置參數。 在不同的包含語句中可以設置不同的值到參數占位符上。比如:
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
這個 SQL 片段可以被包含在其他語句中,例如:
<select id="selectUsers" resultType="map">select<include refid="userColumns"><property name="alias" value="t1"/></include>,<include refid="userColumns"><property name="alias" value="t2"/></include>from some_table t1cross join some_table t2
</select>
然后我們根據剛才的知識寫對應的配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.now.community.community.dao.UserMapper"><sql id="insertFields">userName,password,salt,email,type,status,activation_code,header_url,create_time</sql><sql id="selectFields">id,userName,password,salt,email,type,status,activation_code,header_url,create_time</sql><select id="selectById" resultType="User">select <include refid="selectFields"></include>from userwhere id=#{id}</select><select id="selectByName" resultType="User">select <include refid="selectFields"></include>from userwhere username=#{username}</select><select id="selectByEmail" resultType="User">select <include refid="selectFields"></include>from userwhere email=#{email}</select><insert id="insertUser" parameterType="User" keyProperty="id">insert into user (<include refid="insertFields"></include>)values(#{username},#{password},#{salt},#{email},#{type},#{status},#{activationCode},#{headerUrl},#{createTime})</insert><update id="updateStatus">update user set status=#{status} where id=#{id}</update><update id="updateHeader">update user set header_url=#{headerUrl} where id=#{id}</update><update id="updatePassword">update user set password=#{password} where id=#{id}</update></mapper>
測試效果
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MapperTests {@Autowiredprivate UserMapper userMapper;@Testpublic void testSelectUser(){User user=userMapper.selectById(101);System.out.println(user);user = userMapper.selectByName("liubei");System.out.println(user);user = userMapper.selectByEmail("101@sina.com");System.out.println(user);}@Testpublic void testInsertUser() {User user = new User();user.setUsername("test");user.setPassword("123456");user.setSalt("abc");user.setEmail("test@qq.com");user.setHeaderUrl("E:/101.png");user.setCreateTime(new Date());System.out.println(user.toString());int rows = userMapper.insertUser(user);System.out.println(rows);System.out.println(user.getId());}@Testpublic void updateUser() {int rows = userMapper.updateStatus(150, 1);System.out.println(rows);rows = userMapper.updateHeader(150, "E:/102.png");System.out.println(rows);rows = userMapper.updatePassword(150, "hello");System.out.println(rows);}
}
可以查看結果是完全沒問題的