簡介
mybatis 是一個持久層框架,它讓我們可以方便、解耦地操作數據庫。 相比 hibernate,mybatis 在國內更受歡迎,而且 mybatis 更面向數據庫,可以靈活地對 sql 語句進行優化。
針對 mybatis 的分析,我會拆分成使用、配置、源碼、生成器等部分,都放在 Mybatis 這個系列里,內容將持續更新。這篇博客是系列里的第一篇文章,將從下面兩個問題展開 :
-
為什么要用持久層框架?
-
如何使用 mybatis?
為什么要用持久層框架
在分析如何用之前,我們先來分析為什么要用?和往常一樣,我們只會回答為什么要使用某種類庫,而不會去回答為什么要使用某個具體的類庫。說來慚愧,用了這么久 mybatis,我從來沒有認真思考過這個問題,要不是寫這篇博客的緣故,估計永遠也不會思考。
我們與數據庫的交互過程
首先,先從一張圖開始,這張圖大致描述了我們與數據庫交互的過程。這里我結合查詢員工的例子來說明。
根據分層抽象的思想,接口 B 不應該對外暴露任何的數據庫細節,從調用者的角度看,我們只需要想着從一堆員工里找出符合條件的那幾個就行,而不需要去考慮從哪查、如何查。接口示例如下:
List<Employee> queryListByNameAndGenderAndPhone(String name, Integer gender, String phone);
而在接口 B 里面,邏輯就比較復雜了,需要先將入參對象映射到 sql 里面,然后執行 sql,最后將結果集映射為出參對象。那么,這段邏輯應該怎么實現呢?
使用JDBC實現接口B
在沒有持久層框架之前,最直接的做法就是用 JDBC API 來實現接口 B 的邏輯。代碼大致如下:
public List<Employee> queryListByNameAndGenderAndPhone(String name, Integer gender, String phone) throws SQLException{
String sql = "select * from demo_employee where name = ? and gender = ? and phone = ? and deleted = 0";
// 獲得連接、語句對象
PreparedStatement statement = connection.prepareStatement(sql);
// 入參映射
statement.setString(1, name);
statement.setInt(2, gender);
statement.setString(3, phone);
// 執行sql
ResultSet resultSet = statement.executeQuery();
// 出參映射
List<Employee> employees = new ArrayList<>();
while(resultSet.next()) {
Employee employee = new Employee();
employee.setId(resultSet.getString("id"));
employee.setName(resultSet.getString("name"));
employee.setGender(resultSet.getInt("gender"));
employee.setNo(resultSet.getString("no"));
employee.setAddress(resultSet.getString("address"));
employee.setDeleted(resultSet.getInt("deleted"));
employee.setDepartmentId(resultSet.getString("department_id"));
employee.setPassword(resultSet.getString("password"));
employee.setPhone(resultSet.getString("phone"));
employee.setStatus(resultSet.getInt("status"));
employee.setGmtCreate(resultSet.getDate("gmt_create"));
employee.setGmtModified(resultSet.getDate("gmt_modified"));
employees.add(employee);
}
// 釋放資源
resultSet.close();
statement.close();
return employees;
}
我們會發現,直接使用 JDBC API 操作數據庫還是比較繁瑣,尤其是出入參的映射。如果每個持久層方法都這么搞,麻煩程度可想而知。出於程序員的本能,我們自然會想要優化它。當我們嘗試從這類代碼中抽取共性時,將會發現一些規律:查詢員工也好,查詢部門也好,它們用的 JDBC 代碼幾乎是一模一樣的,要說不一樣的地方,只有三個:
- 我們要執行什么 sql;
- 入參對象如何映射進 sql;
- 結果集如何映射成出參對象。
基於以上思考,這里提供其中一種優化思路:我們可以將 JDBC 代碼抽取為公共方法,而“三個不同“采用單獨配置實現。mybatis、hibernate 就是按照這種思路設計的。
其實,分析到這一步,我們已經得到了答案,使用持久層框架本質上就是為了更簡單地實現接口 B。
使用mybatis實現接口B
接下來,我們看看 mybatis 是如何實現上述思路的。
使用 mybatis 實現 B 接口時,我們不需要寫任何 JDBC 代碼,甚至 B 接口的實現類都不需要我們自己寫,只需要把“三個不同“配置好就行(xml 或注解配置),mybatis 會自動幫我們生成 B 接口的實現類。
以下是 mybatis 注解版的員工查詢,兩個注解就完成了“三個不同”的配置,除此之外,我不需要增加任何多余代碼,是不是相當方便呢?
@Select("select * from demo_employee where name = #{name} and gender = #{gender} and phone = #{phone} and deleted = 0")
@ResultType(Employee.class)
List<Employee> queryListByNameAndGenderAndPhone(@Param("name") String name, @Param("gender") Integer gender, @Param("phone") String phone);
所以,使用持久層框架,可以讓我們更方便、更解耦地操作數據庫。任何的持久層框架,都不應該脫離這個核心。
搞清楚為什么要用之后,我們接着分析如何用。
項目環境
這里先介紹下項目的一些“基礎設施”。為了讓 mybatis 更加純粹,本項目不會引入任何的依賴注入框架,將使用 mybatis 原生的 API。
工程環境
JDK:1.8.0_231
maven:3.6.3
IDE:Spring Tool Suites4 for Eclipse 4.12 (裝有 Mybatipse 插件)
mysql:5.7.28
依賴引入
mybatis 有自帶的數據源,但實際項目中建議還是引入第三方的比較好。這里使用 HikariCP。
<!-- Mybatis -->
<dependency>
<groupId>org.Mybatis</groupId>
<artifactId>Mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!-- 數據庫驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<!-- 數據庫連接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.6.1</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
數據庫腳本
本項目的數據庫概念模型如下。為了盡可能模擬出更多的場景,本項目給出的表會比較復雜,涉及到 4 張主表和 2 張中間表,具體的 sql 腳本也提供好了(腳本路徑):
入門例子
入門例子需要實現:從數據庫中查詢出符合條件的員工。
配置configuration xml文件
首先,需要在 classpath (一般為 src\main\resources 目錄)下新增 mybatis 的主配置文件 mybatis-config.xml,具體文件名無要求,隨便取都行。配置文件的層級結構如下(注意:配置文件里的 xml 節點必須嚴格按以下順序寫,不然會報SAXParseException
):
configuration(配置)
- properties(全局參數)
- settings(全局行為配置)
- typeAliases(類型別名)
- typeHandlers(類型處理器)
- objectFactory(對象工廠)
- plugins(插件)
- environments(環境配置)
- environment(環境變量)
- transactionManager(事務管理器)
- dataSource(數據源)
- environment(環境變量)
- databaseIdProvider(數據庫廠商標識)
- mappers(映射器)
作為入門例子,這里只進行了簡單的配置(實際項目中其實也差不多,一般也只會用到加粗的幾個節點)。
<?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>
<!-- 全局配置 -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
<!-- 配置環境:可以有多個 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事務管理 -->
<transactionManager type="JDBC"/>
<!-- 數據源 -->
<!-- <dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource> -->
<dataSource type="cn.zzs.mybatis.factory.HikariDataSourceFactory" />
</environment>
</environments>
<!-- 映射器 -->
<mappers>
<package name="cn.zzs.mybatis.mapper"/>
</mappers>
</configuration>
以上配置作用如下:
- settings:一些影響 mybatis 行為的全局配置。例如,上面配置了 mapUnderscoreToCamelCase 為 true,在進行結果集映射時,只要對象字段和數據庫字段之間遵循駝峰命名,mybatis 就能自動將它們映射好,而不需要我們手動配置映射。可配置參數見
org.apache.ibatis.builder.xml.XMLConfigBuilder.settingsElement(Properties)
。 - environments:環境配置。這里簡單說下 dataSource,如果使用 mybatis 自帶的數據源,則直接配置數據源參數就行,如果引入第三方數據源,則需要配置你自己重寫的
org.apache.ibatis.datasource.DataSourceFactory
實現類。建議使用第三方數據源。 - mappers:其實就是告訴 mybatis 你的 Mapper 接口和 Mapper xml 文件放在哪里。這里需要注意下,如果我們把 Mapper 接口放在 src/main/java 下的 cn.zzs.mybatis.mapper,那么 Mapper xml 文件就應該放在 src\main\resources 下的 cn\zzs\mybatis\mapper。當然,你也可以把 Mapper xml 文件放在 src/main/java 下的 cn.zzs.mybatis.mapper(不建議),這種情況你就需要在 pom.xml 文件中額外增加以下配置。事實上,你怎么騷操作都可以,只要保證項目編譯打包后 Mapper 接口和 xml 文件在你指定的路徑下就行。
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>false</filtering>
<includes>
<include>**/mapper/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**</include>
</includes>
</resource>
</resources>
</build>
創建Mapper接口
然后,我們需要在指定的包下創建好 EmployeeMapper 接口。上面的配置文件中,我們已經指定 Mapper 接口放在cn.zzs.Mybatis.mapper
包下面,所以要保持一致才行。這里的@Param
用來給入參配置別名。
public interface EmployeeMapper {
List<Employee> queryListByNameAndGenderAndPhone(@Param("name") String name, @Param("gender") Integer gender, @Param("phone") String phone);
}
配置mapper xml文件
接着,我們需要在指定路徑下創建好 EmployeeMapper.xml 文件。EmployeeMapper.xml 只有很少的幾個頂級元素:
cache
– 該命名空間的緩存配置。cache-ref
– 引用其它命名空間的緩存配置。resultMap
– 描述如何將結果集映射為出參對象。parameterMap
– 此元素已被廢棄,並可能在將來被移除!sql
– 可被其它語句引用的可重用語句塊。insert
– 映射插入語句。update
– 映射更新語句。delete
– 映射刪除語句。select
– 映射查詢語句。
作為入門例子,這里只給出了簡單的配置。其中,select 節點中配置了 queryListByNameAndGenderAndPhone 方法的“三個不同”,即需要執行的 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="cn.zzs.mybatis.mapper.EmployeeMapper">
<select id="queryListByNameAndGenderAndPhone" parameterType="map" resultType="cn.zzs.mybatis.entity.Employee">
select * from demo_employee where name = #{name} and gender = #{gender} and phone = #{phone} and deleted = 0
</select>
</mapper>
在本例中,我們指定了把結果集映射為Employee
,通常情況下,只要對象字段和數據庫字段遵循駝峰命名,都能自動映射成功。但是,總會有例外嘛,如果我的字段命名不遵循駝峰命名怎么辦?這里我給出了兩種可選方案:
<!-- 特殊字段的映射方法1:resultMap -->
<resultMap id ="BaseResultMap" type = "cn.zzs.mybatis.entity.Employee">
<result column="gmt_create" property="create" />
</resultMap>
<select id="queryListByNameAndGenderAndPhone" parameterType="map" resultMap="BaseResultMap">
select * from demo_employee where name = #{name} and gender = #{gender} and phone = #{phone} and deleted = 0
</select>
<!-- 特殊字段的映射方法2:as -->
<select id="queryListByNameAndGenderAndPhone" parameterType="map" resultType="cn.zzs.mybatis.entity.Employee">
select *, gmt_create as `create` from demo_employee where name = #{name} and gender = #{gender} and phone = #{phone} and deleted = 0
</select>
注意下參數符號 #{name}, 它告訴 mybatis 創建一個預處理語句(PreparedStatement)參數,在 JDBC 中,這樣的一個參數在 SQL 中會由一個“?”來標識,並被傳遞到一個新的預處理語句中。不過有時你就是想直接在 SQL 語句中直接插入一個不轉義的字符串。 這時候你可以使用 “$” 字符:
ORDER BY ${columnName}
編寫測試類
在以下代碼中,存在四個主要對象:
SqlSessionFactoryBuilder
:一旦創建了 SqlSessionFactory,就不再需要它了,因此 SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域。SqlSessionFactory
:一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建另一個實例, 因此 SqlSessionFactory 的最佳作用域是應用作用域。SqlSession
:每個線程都應該有它自己的 SqlSession 實例。SqlSession 實例不是線程安全的,因此不能被共享,所以它的最佳的作用域是請求或方法作用域。 SqlSession 的作用類似於 JDBC 的 Connection ,使用完后必須 close。EmployeeMapper
:默認情況下,因為 EmployeeMapper 和 SqlSession 綁定在一起,所以,EmployeeMapper 也是線程不安全的。有的人可能會問,既然 EmployeeMapper 是線程不安全的,那為什么 spring 把它作為單例使用?其實並不矛盾,EmployeeMapper 是否線程安全取決於 SqlSession,而 spring 中使用的 SqlSession 實現類是線程安全的,原理也並不復雜,具體見org.mybatis.spring.SqlSessionTemplate
。
public class EmployeeMapperTest {
@Test
public void testSample() throws SQLException {
// 加載配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 獲取sqlSession
// 第一種獲取sqlSession方法:同一線程每次獲取都是同一個sqlSession
/*SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(inputStream);
SqlSession sqlSession = sqlSessionManager.openSession();*/
// 第二種獲取sqlSession方法:同一線程每次獲取都是不同的sqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 獲取Mapper代理類
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 執行查詢
List<Employee> employees = employeeMapper.queryListByNameAndGenderAndPhone("zzs001", 1, "18826****41");
// 測試
assertTrue(!CollectionUtils.isEmpty(employees));
employees.stream().forEach(System.err::println);
// 釋放資源
sqlSession.close();
}
}
運行測試
運行上面的方法,會在控制台輸出了 sql 和匹配的員工數據。
==> Preparing: select * from demo_employee where name = ? and gender = ? and phone = ? and deleted = 0
==> Parameters: zzs001(String), 1(Integer), 18826****41(String)
<== Total: 1
Employee(id=cc6b08506cdb11ea802000fffc35d9fa, name=zzs001, gender=1, no=zzs001, password=666666, phone=18826****41, address=北京, status=1, deleted=0, departmentId=65684a126cd811ea802000fffc35d9fa, gmtCreate=Wed Sep 04 21:48:28 CST 2019, gmtModified=Wed Mar 25 10:44:51 CST 2020)
高級條件查詢
在實際項目中,我們經常需要用到高級條件查詢。這類查詢和入門例子的查詢最大的不同在於,高級條件查詢的某個條件可能為空,所以,在配置入參映射時需要進行判斷。mybatis 提供了豐富的動態 sql 語法以支持此類入參映射,如下:
<select id = "queryByCondition" parameterType="cn.zzs.mybatis.condition.EmployeeCondition" resultType="cn.zzs.mybatis.entity.Employee">
select e.* from demo_employee e where 1 = 1
<!-- 一般條件 -->
<if test="con.deleted != null">
and e.deleted = #{con.deleted}
</if>
<!-- 字符類條件 -->
<if test="con.name != null and con.name != ''">
and e.name = #{con.name}
</if>
<!-- 大於類條件 -->
<if test="con.createStart != null">
and e.gmt_create > #{con.createStart}
</if>
<!-- 集合類條件 -->
<if test="con.phoneInclude != null and con.phoneInclude.size() > 0">
and e.phone in
<foreach collection="con.phoneInclude" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
</select>
多表查詢
大多數情況下,我們對外提供的查詢員工接口,只是返回員工的字段是遠遠不夠的,還需要返回員工關聯對象的字段,例如,員工所屬部門、員工管理的菜單等等。
這時,我們的查詢將涉及到多個表,針對這種情況,我認為有三種解決方案:
- 手動分表查詢;
- 自動分表查詢;
- 聯表查詢。
手動分表查詢
手動分表查詢代碼如下。顧名思義,我需要在代碼中單獨查每個表,再將查到的數據組裝起來。
@Test
public void testMultiTableQuery1() throws Exception {
// 先查員工
Employee employee = employeeMapper.queryById("cc6b08506cdb11ea802000fffc35d9fe");
// 再查部門
Department department = departmentMapper.queryById(employee.getDepartmentId());
// 組裝數據
EmployeeVO employeeVO = new EmployeeVO();
BeanUtils.copyProperties(employeeVO, employee);
employeeVO.setDepartmentName(department.getName());
employeeVO.setDepartmentNo(department.getNo());
// 測試
System.err.println(employeeVO);
}
這種方案的優點在於;
- 如果后期多表分庫,代碼無需改動;
- Mapper 接口中只要提供通用的基本方法就行,無需增加任何個性化方法;
- 在應對各種場景的不同 VO 時,這種方案非常靈活。
至於缺點嘛,就是會遇到N+1 查詢問題,列表查詢尤甚。
自動分表查詢
自動分表查詢的代碼如下。與手動分表查詢不同,這里的查部門,不是我們自己手動查,而是由 mybatis 幫我們查。
@Test
public void testMultiTableQuery2() throws Exception {
// 查員工(部門也會一起查)
Employee employee = employeeMapper.queryById("cc6b08506cdb11ea802000fffc35d9fe");
// 取出部門對象
Department department = employee.getDepartment();
// 組裝數據
EmployeeVO employeeVO = new EmployeeVO();
BeanUtils.copyProperties(employeeVO, employee);
employeeVO.setDepartmentName(department.getName());
employeeVO.setDepartmentNo(department.getNo());
// 測試
System.err.println(employeeVO);
}
與手動分表查詢不同,這里第一步返回的 employee 是一個非常“完整”的對象,它包括部門、角色等,如果你使用過 hibernate,對此應該並不陌生。所以,在某種程度上,這種方案更加面向對象。為了查出這樣一個“完整”的對象,我們需要進行如下配置:
<resultMap id = "EmployResultMap" type = "cn.zzs.mybatis.entity.Employee">
<result column="id" property="id"/>
<result column="department_id" property="departmentId"/>
<association
property="department"
column="department_id"
select="cn.zzs.mybatis.mapper.DepartmentMapper.queryById"/>
<collection
property="roles"
column="id"
select="cn.zzs.mybatis.mapper.RoleMapper.queryByEmployeeId"
/>
</resultMap>
<!-- 多表查詢1和2 -->
<select id = "queryById" parameterType = "string" resultMap = "EmployResultMap">
select e.*
from demo_employee e
where e.id = #{value}
</select>
需要注意的是,自動分表查詢最好結合延遲加載使用,即當調用 getDepartment 時才觸發查部門的動作。因為不是每種場景都需要查出那么“完整”的對象,延遲加載很好地避免不必要的性能開銷。延遲加載的配置如下(默認不開啟):
<!-- 全局配置 -->
<settings>
<setting name="lazyLoadingEnabled" value="true" />
</settings>
和手動多表查詢一樣,自動分表查詢不需要在 Mapper 中增加個性化方法,但是呢,因為耦合多表的關聯,所以,如果后期多表分庫,改動就比較大了。而且,在應對各種場景的 VO 時,這種方案靈活性要比手動多表查詢稍差。
當然,這種方案也有N+1查詢問題。
聯表查詢
聯表查詢代碼如下。這種方案不需要分表查詢,多表數據一次查出。
@Test
public void testMultiTableQuery3() {
// 執行查詢
EmployeeVO employeeVO = employeeMapper.queryVOById("cc6b08506cdb11ea802000fffc35d9fe");
// 測試
System.err.println(employeeVO);
}
具體做法就是采用聯表查詢,xml 配置如下。
<select id = "queryVOById" parameterType = "string" resultType = "cn.zzs.mybatis.vo.EmployeeVO">
select e.*, d.name as departmentName, d.no as departmentNo
from demo_employee e left join demo_department d on e.department_id = d.id
where e.id = #{value}
</select>
因為不需要分表查詢,所以聯表查詢可以避免N+1查詢問題,所以,相比前兩種方案,它的性能一般是最好的。但是呢,聯表查詢嚴重耦合了多表的關聯,如果后期多表分庫,改動會比較大。而且,這種方案的靈活性非常差,它幾乎需要在 Mapper 接口中為每個 VO 提供個性化方法。
以上三種方案,各有優缺點,實際項目中采用哪種,需要結合多表分庫、性能等因素具體分析。
分頁查詢
最后再簡單說下分頁查詢吧。其實,mybatis 提供了RowBounds
來支持分頁,但是呢,這種分頁不是數據庫分頁,而是應用內存分頁,非常的不友好,所以,分頁查詢還是得另辟蹊徑。
引入插件依賴
本項目使用 pagehelper 插件來實現分頁,首先,我們需要在 pom.xml 文件中增加以下依賴。
<!-- 分頁插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
修改 mybatis 主配置文件
然后,在 mybatis 主配置文件中增加 plugins 元素,並配置分頁插件。
<!-- 配置插件 -->
<plugins>
<!-- 分頁插件 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
編寫測試方法
這里調用 startPage 相當於告訴分頁插件對**本線程的下一個列表查詢 sql **進行分頁處理。
@Test
public void testPageQuery() {
// 構建條件
EmployeeCondition con = new EmployeeCondition();
con.setDeleted(0);
// 設置分頁信息
PageHelper.startPage(0, 3);
// 執行查詢
List<Employee> employees = employeeMapper.queryByCondition(con);
// 遍歷結果
employees.forEach(System.err::println);
// 封裝分頁模型
PageInfo<Employee> pageInfo = new PageInfo<>(employees);
// 取分頁模型的數據
System.err.println("結果總數:" + pageInfo.getTotal());
}
測試
運行上面的方法,會在控制台輸出了 sql 和匹配的員工數據。我們發現,這里竟然多了一句查數量的 sql,而且,列表查詢的 sql 被嵌入了分頁參數。到底是怎么做到的呢?這個問題等我們分析源碼的時候再解答吧。
==> Preparing: SELECT count(0) FROM demo_employee e WHERE 1 = 1 AND e.deleted = ?
==> Parameters: 0(Integer)
<== Total: 1
==> Preparing: select e.id,e.`name`,e.gender,e.no,e.password,e.phone,e.address,e.status,e.deleted,e.department_id,e.gmt_create,e.gmt_modified from demo_employee e where 1 = 1 and e.deleted = ? LIMIT ?
==> Parameters: 0(Integer), 3(Integer)
<== Total: 3
Employee(id=2e18f6560b25473480af987141eccd02, name=zzs005, gender=1, no=zzs005, password=admin, phone=18826****41, address=廣東, status=1, deleted=0, departmentId=94e2d2e56cd811ea802000fffc35d9fa, gmtCreate=Sat Mar 28 00:00:00 CST 2020, gmtModified=Sat Mar 28 00:00:00 CST 2020, department=null, roles=null)
Employee(id=cc6b08506cdb11ea802000fffc35d9fa, name=zzs001, gender=1, no=zzs001, password=666666, phone=18826****42, address=北京, status=1, deleted=0, departmentId=65684a126cd811ea802000fffc35d9fa, gmtCreate=Wed Sep 04 21:48:28 CST 2019, gmtModified=Wed Mar 25 10:44:51 CST 2020, department=null, roles=null)
Employee(id=cc6b08506cdb11ea802000fffc35d9fb, name=zzs002, gender=1, no=zzs002, password=123456, phone=18826****43, address=廣東, status=1, deleted=0, departmentId=65684a126cd811ea802000fffc35d9fa, gmtCreate=Thu Aug 01 21:49:43 CST 2019, gmtModified=Mon Sep 02 21:49:49 CST 2019, department=null, roles=null)
結果總數:9
結語
以上基本講完為什么要使用持久層框架以及如何使用 mybatis。后續發現其他有趣的地方再做補充,也歡迎大家指正不足的地方。
最后,感謝閱讀。
參考資料
2021-09-29更改
相關源碼請移步:mybatis-demo
本文為原創文章,轉載請附上原文出處鏈接:https://www.cnblogs.com/ZhangZiSheng001/p/12603885.html