MyBatis學習筆記(一)
MyBatis(一)
MyBatis學習筆記(一), 內容包括:
- MyBatis入門案例
- MyBatis核心配置文件結構說明
- sql映射文件結構說明
- 查詢結果封裝resultMap
- 多對一查詢association、一對多查詢collection
學習視頻:尚硅谷雷豐陽老師MyBatis
https://www.bilibili.com/video/BV1bb411A7bD
1. MyBatis簡介
連接數據庫的方法:
工具: JDBC→Dbutils(QueryRunner)→JdbcTemplate
框架: 整體解決方案
Hibernate: 全自動全映射ORM(Object Relation Mapping)框架,旨在消除SQL
缺點:
- 長難復雜SQL,對于Hibernate而言處理也不容易
- 內部自動生產的SQL,不容易做特殊優化。對開發人員而言,核心sql還是需要自己優化( HQL)
- 基于全映射的全自動框架,大量字段的POJO進行部分映射時比較困難,導致數據庫性能下降
SQL和Java編碼分開,功能邊界清晰,一個專注業務、一個專注數據。
MyBatis: 半自動,輕量級的框架,sql語句提取到配置文件中編寫
-
MyBatis 是一個半自動化的持久化層框架,支持自定義 SQL、存儲過程以及高級映射。
-
MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。
-
MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO映射成數據庫中的記錄。
-
Maven 依賴:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>x.x.x</version> </dependency>
官方文檔:https://mybatis.org/mybatis-3/zh/index.html
2. MyBatis案例之HelloWorld
核心步驟:
- 數據庫表與對應的實體類Employee、接口EmployeeMapper
- 全局配置文件(核心配置文件)mybatis-config.xml
- 根據全局配置文件創建SqlSessionFactory對象,SqlSessionFactoryBulid→SqlSessionFactory→SqlSession
- sql映射文件EmployeeMapper.xml,配置了每一個sql,以及sql的封裝規則,需要在全局配置文件中注冊sql映射文件
- 由sqlSession對象實例執行sql語句,或者獲取接口的代理對象執行
2.1 數據庫環境
創建tbl_employee表并插入三條數據:
CREATE TABLE tbl_employee(
id INT(10) PRIMARY KEY AUTO_INCREMENT,
last_name VARCHAR(255),
gender CHAR(1),
email VARCHAR(255)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO tbl_employee VALUES
(1,"zhansgan",0,"zhangsan@qq.com"),
(2,"lisi",0,"lisi@163.com"),
(3,"wangwu",1,"wangwu@126.com");
2.2 創建實體類
- 新建一個普通的Maven項目;
- 在pom.xml文件中導入Maven依賴,包括:mysql-connector-java,mybatisjuint,log4j
- 創建與數據庫表對應的實體類Employee,名稱與數據庫表的字段保持一致。如果不一致查詢會出問題,可以用別名解決
- 項目依賴:
<dependencies>
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<!--log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
- 實體類com.xiao.pojo.Employee:
public class Employee {
private Integer id;
private String lastName; //注意,該名稱與數據庫表的字段名不一致,查詢會出現問題,可以用別名解決
private String email;
private String gender;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
2.3 MyBatis核心配置文件
- 在resources目錄下編寫mybatis核心配置文件: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">
<!--mybatis核心配置文件-->
<configuration>
<!--配置環境組,選擇默認的環境id-->
<environments default="development">
<!--配置單個環境并指定id為development-->
<!--可以同時配置多個環境,但是只能選擇一個使用-->
<environment id="development">
<!--配置事務管理類型,JDBC-->
<transactionManager type="JDBC"/>
<!--配置連接池POOLED-->
<dataSource type="POOLED">
<!--數據庫連接池4個參數-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--配置映射,注冊Mapper文件-->
<mappers>
<mapper resource="com/xiao/dao/EmployeeMapper.xml"/>
</mappers>
</configuration>
注意:
在xml配置文件中,url中的 &符號需要寫成 &
2.4 sql映射文件
在resources目錄下表編寫sql映射文件:EmployeeMapper.xml
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">
<!--namespace:名稱空間,可以綁定一個Mapper接口-->
<!--編寫sql語句-->
<!--id:唯一標識,如果有Mapper接口文件,需要對應其中的方法名-->
<!--resultType:對應返回結果類型-->
<!--#{id}:從傳遞過來的參數中取出id值-->
<mapper namespace="com.xiao.dao.EmployeeMapper">
<select id="selectEmp" resultType="com.xiao.pojo.Employee">
select * from tbl_employee where id = #{id}
</select>
</mapper>
2.5 測試
根據全局配置文件創建SqlSessionFactory對象,SqlSessionFactoryBulid→SqlSessionFactory→SqlSession。由sqlSession對象實例執行sql語句。
public class MyBatisTest {
@Test
public void test() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//由SqlSessionFactoryBuilder對象獲取SqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//由SqlSession工廠獲得SqlSession對象,使用其進行增刪改查
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//sqlSession,直接執行已經映射的sql語句
//selectOne()中兩個參數:sql的唯一標識(對應sql映射文件中的namespace.id)和執行sql需要的參數
Employee employee = sqlSession.selectOne("com.xiao.dao.EmployeeMapper.selectEmp", 1);
System.out.println(employee);
} finally {
//一個sqlSession就是和數據庫的一次會話,使用完之后需要關閉資源
sqlSession.close();
}
}
}
查詢結果:
Employee{id=1, lastName='null', email='zhangsan@qq.com', gender='0'}
由于實體類的成員變量名和字段名不一致,因此lastName查詢結果為null,可以在sql語句中取別名解決,也可以在sql映射文件中配置,后面會講到。
修改sql語句:
select id,last_name lastName,gender,email from tbl_employee where id = #{id}
查詢結果:
Employee{id=1, lastName='zhansgan', email='zhangsan@qq.com', gender='0'}
2.6 接口文件
一種更有效的方法:創建接口文件com.xiao.dao.EmployeeMapper與sql映射文件綁定,在接口文件中定義方法,可以聲明操作的返回值和參數。
注意:
- sql映射文件中的namesapce要與對應的接口文件全路徑名一致
- sql映射文件中的sql語句標簽中的id要與對應的接口文件中的方法名一致
public interface EmployeeMapper {
//定義一個查詢方法
Employee selectEmployeeById(Integer id);
}
測試文件:
try { //獲得接口文件的代理對象,執行方法
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
System.out.println(mapper.selectEmployeeById(1));
} finally {
sqlSession.close();
}
3. 幾個類的說明
3.1 SqlSessionFactoryBuilder
-
構造器SqlSessionFactoryBuilder ,可以從 XML 配置文件或一個預先配置的 Configuration 實例來構建出 SqlSessionFactory 實例。
-
方法:
build(InputStream in)
3.2 SqlSessionFactory
- SqlSession工廠,每個基于 MyBatis 的應用都以一個 SqlSessionFactory 的實例為核心,由SqlSessionFactoryBuilder類創建。
- SqlSessionFactory類可以用于創建SqlSession類,方法:
openSession()
,傳入參數true可以設置為自動提交事務。
3.3 SqlSession
- SqlSession代表和數據庫的一次會話,用完必須關閉,是非線程安全的。可以從SqlSessionFactory中獲得 SqlSession 的實例;
- SqlSession 提供了在數據庫執行 SQL 命令所需的所有方法,可以通過 SqlSession 實例來獲得映射器接口的代理對象,即接口和xml文件進行綁定。方法:
getMapper()
,需要傳入dao接口的class類型參數UserDao.class
3.4 作用域(Scope)和生命周期
-
SqlSessionFactoryBuilder:
可以理解為數據庫連接池對象,一旦創建了 SqlSessionFactory,就不再需要它了,因此最佳作用域是方法作用域(也就是局部方法變量);
-
SqlSessionFactory:
可以理解為數據庫連接對象,一旦被創建就應該在應用的運行期間一直存在,因此 SqlSessionFactory 的最佳作用域是應用作用域。使用單例模式或者靜態單例模式。
-
SqlSession:
連接到連接池的一個請求,每個線程都有它自己的 SqlSession 實例。SqlSession 的實例不是線程安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。
為了確保每次都能執行關閉操作,把關閉操作放到 finally 塊中。
4. 全局配置文件
約束文件:
<?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">
MyBatis 的配置文件包含了會深深影響 MyBatis 行為的設置和屬性信息。 配置文檔的頂層結構如下,必須按順序配置
- configuration(配置)
- properties(屬性)
- settings(設置)
- typeAliases(類型別名)
- typeHandlers(類型處理器)
- objectFactory(對象工廠)
- plugins(插件)
- environments(環境配置)
- environment(環境變量)
- transactionManager(事務管理器)
- dataSource(數據源)
- environment(環境變量)
- databaseIdProvider(數據庫廠商標識)
- mappers(映射器)
4.1 屬性(properties)
通過properties屬性可以實現引用配置文件,這些屬性都是可以外部配置且可動態替換的。
- 可以在核心配置文件中配置數據庫連接池的4個參數,設置好的參數可以在整個配置文件中替換需要動態配置的屬性值:
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
....
<!--使用${name}引用相應的值-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
....
-
也可以引入外部的配置文件,例如編寫一個外部的配置文件db.properties,然后在核心配置文件中引入:
- resource:引用類路徑下的資源
- url:引用網絡路徑或者磁盤路徑下的資源
#外部配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=root
核心配置文件:
<!--在核心配置文件中引入外部配置文件,然后就可以使用${name}引用-->
<properties resource="db.properties">
</properties>
【注意事項】:如果一個屬性在不只一個地方進行了配置,那么,MyBatis 將按照下面的順序來加載:
- 首先讀取在 properties 元素體內指定的屬性。
- 然后根據 properties 元素中的 resource 屬性讀取類路徑下屬性文件,或根據 url 屬性指定的路徑讀取屬性文件,并覆蓋之前讀取過的同名屬性。
- 最后讀取作為方法參數傳遞的屬性,并覆蓋之前讀取過的同名屬性。
因此,通過方法參數傳遞的屬性具有最高優先級,resource/url 屬性中指定的配置文件次之,最低優先級的則是 properties 元素中指定的屬性。
5.2 設置(settings)
是 MyBatis 中極為重要的調整設置,它們會改變 MyBatis 的運行時行為。
常用設置:
- mapUnderscoreToCamelCase:是否開啟駝峰命名自動映射,即從經典數據庫列名 A_COLUMN 映射到經典 Java 屬性名 aColumn,默認為flase
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
- jdbcTypeForNull:當沒有為參數指定特定的 JDBC 類型時,空值的默認 JDBC 類型。默認是 OTHER,但是如果用Oracle數據庫,需要改為NULL
- autoMappingBehavior:指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示關閉自動映射;PARTIAL 只會自動映射沒有定義嵌套結果映射的字段。 FULL 會自動映射任何復雜的結果集(無論是否嵌套),默認為PARTIAL
- lazyLoadingEnabled:延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 特定關聯關系中可通過設置
fetchType
屬性來覆蓋該項的開關狀態。默認是false - aggressiveLazyLoading:開啟時,任一方法的調用都會加載該對象的所有延遲加載屬性。 否則,每個延遲加載屬性會按需加載。
5.3 類型別名(typeAliases)
類型別名可為 Java 類型設置一個縮寫名字。 僅用于 XML 配置,意在降低冗余的全限定類名書寫。別名不區分大小寫
- 單個類起別名,
<typeAlias>
標簽:- type:指定要起別名的類型全類名,默認是類名小寫employee(其實不區分大小寫)
- alias:指定新的別名
<typeAliases>
<!-- type:指定要起別名的類型全類名,默認是類名小寫employee-->
<!-- alias:指定新的別名-->
<typeAlias type="com.xiao.pojo.Employee"/>
</typeAliases>
- 批量起別名,用
<package>
標簽,
<typeAliases>
<package name="com.xiao.pojo"/>
</typeAliases>
- 批量情況下,如果指定包下的子包中有同名的類,會產生沖突,可以使用注解解決:在實體類上注解
@Alias
,則別名為其注解值:
@Alias("Emp")
public class Employee {
...
}
常見的 Java 類型內建的類型別名。它們都是不區分大小寫的,自定義的別名不要與其重復。
基本類型前面加下劃線,引用類型首字母小寫:
別名 | 映射的類型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
5.4 類型處理器(typeHandlers)
數據庫里的字段類型與Java的數據類型進行映射。
在設置預處理語句(PreparedStatement)中的參數或從結果集中取出一個值時, 都會用類型處理器將獲取到的值以合適的方式轉換成 Java 類型。
5.5 插件(plugins)
MyBatis 允許在映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- 參數處理器:ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- sql語句處理器:StatementHandler (prepare, parameterize, batch, update, query)
5.6 環境配置(environments)
MyBatis 可以配置成適應多種環境,可以配置多個環境,但每個 SqlSessionFactory 實例只能選擇一種環境。 default參數指定使用某種環境。
- transactionManager:事務管理器,有兩種類型,type="[JDBC|MANAGED]",默認的事務管理器就是JDBC。也可以自定義事務管理器:實現TransactionFactory接口,type指定為全類名
- dataSource:數據源,有三種內建的數據源類型, type="[UNPOOLED|POOLED|JNDI]"),也可以自己實現DataSourceFactory接口自定義數據源
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<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>
【注意事項】:
- 默認使用的環境 id(比如:default=“development”)。
- 每個 environment 元素定義的環境 id(比如:id=“development”)。
- 事務管理器的配置(比如:type=“JDBC”)。
- 數據源的配置(比如:type=“POOLED”)。
5.7 數據庫廠商標識(databaseIdProvider)
MyBatis 可以根據不同的數據庫廠商執行不同的語句,考慮了移植性。
得到數據庫廠商的標識(驅動getDatabaseProductName()),MyBatis就能根據數據庫廠商標識來執行不同的sql。
- 為不同的數據庫的廠商標識取別名:
- 查詢語句標簽中指定databaseId元素值,會優先執行帶該標識的,不帶標簽的語句會舍棄
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
5.8 映射器(mappers)
需要告訴 MyBatis 到哪里去找到 sql語句。可以使用相對于類路徑的資源引用,或完全限定資源定位符(包括 file:///
形式的 URL),或類名和包名等。
- resource:指定sql映射文件的路徑
- url:使用網上的資源或者使用本地磁盤路徑上的文件
- class:注冊接口,如果有sql映射文件,映射文件必須和接口同目錄同名;也可以在接口上寫注解,這樣就不需要sql映射文件
- package:批量注冊,將包內的映射器接口實現全部注冊為映射器,作用同class
<mappers>
<!-- 使用相對于類路徑的資源引用 -->
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<!-- 使用完全限定資源定位符(URL) -->
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<!-- 使用映射器接口實現類的完全限定類名 -->
<mapper class="org.mybatis.builder.AuthorMapper"/>
<!-- 將包內的映射器接口實現全部注冊為映射器 -->
<package name="org.mybatis.builder"/>
</mappers>
在dao接口文件中,用注解實現查詢:
public interface EmployeeMapper {
//定義一個查詢方法
@Select("select * from tbl_employee where id = #{id}")
Employee selectEmployeeById(Integer id);
}
【一個小坑】:
- class:注冊接口,如果有sql映射文件,映射文件必須和接口同目錄同名
- 在resources中建立多級目錄時,用 / 隔開,但是創建完后顯示的是 . 。如果新建目錄時用 .會出錯。
5. 映射文件
映射文件指導MyBatis如何進行數據庫增刪改。
5.1 基本的增刪改查
- insert –- 映射插入語句
- update –- 映射更新語句
- delete –- 映射刪除語句
- select –- 映射查詢語句
- 首先在接口文件com.xiao.dao.EmployeeMapper中聲明對應的增刪改查方法,
同時可以設置返回值類型Integer、Long、Boolean,表示被影響的行數
public interface EmployeeMapper {
//定義一個查詢方法
@Select("select * from tbl_employee where id = #{id}")
Employee selectEmployeeById(Integer id);
//增加
void addEmp(Employee employee);
//根據id刪除
void deleteEmpById(Integer id);
//修改
void updateEmp(Employee employee);
}
- 然后在sql映射文件EmployeeMapper.xml中編寫相應的sql語句
<!--parameterType:參數類型,可以省略-->
<insert id="addEmp" parameterType="employee">
insert into tbl_employee (last_name, gender, email) value (#{lastName},#{gender},#{email})
</insert>
<delete id="deleteEmpById">
delete from tbl_employee where id = #{id}
</delete>
<update id="updateEmp">
update tbl_employee set last_name=#{lastName},email=#{email},gender=#{gender}
where id = #{id}
</update>
- 測試:
public void test2() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//默認不自動提交事務,需要手動提交,或者構造方法傳入true
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//增
mapper.addEmp(new Employee(4,"林青霞","lingqingxia@163.com","1"));
//刪
mapper.deleteEmpById(2);
//改
mapper.updateEmp(new Employee(1,"張柏芝","zhangbozhi@qq.com","1"));
// 手動提交事務
sqlSession.commit();
} finally {
sqlSession.close();
}
}
注意事項:增刪改需要提交事務
sqlSessionFactory.openSession():需要手動提交, sqlSession.commit();
sqlSessionFactory.openSession(true)是自動提交事務的
5.2 獲取自增主鍵的值
MySQL支持自增主鍵,在MyBatis中也是使用statement.getGeneratedKeys()獲取自增主鍵的值。
用法,insert標簽中:
- useGeneratedKeys=“true”:使用自增主鍵獲取主鍵值策略
- keyProperty=“id”:獲取到的主鍵封裝給JavaBean的id屬性
<!--useGeneratedKeys="true":使用自增主鍵獲取主鍵值策略-->
<!--keyProperty="id":獲取到的主鍵封裝給JavaBean的id屬性-->
<insert id="addEmp" parameterType="employee" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee (last_name, gender, email) value (#{lastName},#{gender},#{email})
</insert>
測試:employee.getId()方法可以獲取到自增的主鍵值
Employee employee = new Employee(null,"Tom","tom@126.com","0");
mapper.addEmp(employee);
System.out.println(employee.getId());
5.3 參數處理
取參數的方式:#{參數名}
01 單個參數
MyBatis不會做處理,傳入id,sql語句中寫 #{idabc} 也能取到參數
02 多個參數
會被封裝成一個map,#{}就是從map中獲取指定key值的value。
key:param1,…paramN,或者參數的索引也可以
value:傳入的參數值
如果按照之前的寫法會報異常:
Employee employee = mapper.selectEmpByIdAndName(6, "Tom");
<select id="selectEmpByIdAndName" resultType="employee">
select * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>
BindingException: Parameter ‘id’ not found. Available parameters are [arg1, arg0, param1, param2]
正確寫法,但是一般不這么用:
select * from tbl_employee where id = #{param1} and last_name=#{param2}
常規用法:在接口方法中加注解@Param
: 相當于key中保存的是@Param注解指定的值
Employee selectEmpByIdAndName(@Param("id") Integer id, @Param("lastName") String lastName);
03 傳入對象
- 如果多個參數正好是業務邏輯的數據模型,就可以直接傳入POJO,#{屬性名}取出傳入的POJO的屬性值
- 如果不是業務模型中的數據,沒有對應的POJO,也可以傳入map,#{key}取出map中對應的值,或者編寫一個TO(Transfer Object )數據傳輸對象。
//封裝傳入參數為Map,查詢
Employee selectEmpByMap(Map<String,Object> map);
HashMap<String,Object> map = new HashMap<>();
//注意sql語句中#{id},#{lastName}與map中的鍵字段名稱一一對應
map.put("id",6);
map.put("lastName","Tom");
Employee employee = mapper.selectEmpByMap(map);
System.out.println(employee);
select * from tbl_employee where id = #{id} and last_name=#{lastName}
04 幾種情況小結
-
Employee getEmp(@Param("id") Integer id, String lastName)
取值:id ==> #{id}或者#{param1},lastName ==> #{param2} -
Employee getEmp(Integer id, @Param("e") Employee emp)
取值:id ==> #{param1},lastName ==> #{e.lastName}或者 #{param2.lastName} -
如果傳入的是Collection類型或者是數組,則會把傳入的集合或者數組封裝在map中
key:collection,list,array
Employee getEmp(List<Integer> ids)
取值:取出第一個id的值 #{list[0]}
總結:參數多時封裝成map,結合@Param指定封裝時使用的key,#{key}取出map中的值
05 #{}和${}的區別
#{}
:是以預編譯的形式,即占位符,將參數設置到sql語句中,類似于JDBC中的PreparedStament,防止sql注入
${}
:取出的值直接拼裝在sql語句中,會有安全問題。
原生JDBC中不支持占位符的地方就可以用${取值},比如表名、排序字段等
select * from ${year}_salary where …
select * from tbl_employee order by ${name}
5.4 select的結果映射
01 返回的是集合
select語句中resultType寫的是集合中元素的類型
//查詢所有,返回一個集合
List<Employee> selectAll();
<select id="selectAll" resultType="employee">
select * from tbl_employee
</select>
測試:
List<Employee> employees = mapper.selectAll();
for (Employee employee : employees) {
System.out.println(employee);
}
結果:
Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1'}
Employee{id=3, lastName='wangwu', email='wangwu@126.com', gender='0'}
Employee{id=5, lastName='林青霞', email='lingqingxia@163.com', gender='1'}
Employee{id=6, lastName='Tom', email='tom@126.com', gender='0'}
02 返回一條記錄的map
如果想把一條記錄的返回值類型封裝為map,key值為字段名,value值。則指定resultType為map,查詢到的結果會將指定的列映射到Map 的鍵上,結果映射到值上。
//返回一條記錄的map,key是列名,值就是對應的值
Map<String,Object> selectEmpByIdToMap(Integer id);
<!--查詢并返回一條記錄的map-->
<select id="selectEmpByIdToMap" resultType="map">
select * from tbl_employee where id = #{id}
</select>
測試:
Map<String, Object> map = mapper.selectEmpByIdToMap(1);
System.out.println(map);
結果:
{gender=1, last_name=張曼玉, id=1, email=zhangmanyu@163.com}
03 多條記錄封裝為map
如果想封裝多條記錄到map中,key是主鍵值,value是JavaBean對象,則在接口方法上使用@MapKey
注解指定key的屬性; resultType依然為map中的value類型
//返回多條記錄封裝到map中,key是主鍵值,value是JavaBean對象
//@MapKey;指定返回的map的key
@MapKey("id")
Map<Integer,Employee> selectAllToMap();
<!-- 返回多條記錄封裝到map中,key是主鍵值,value是JavaBean對象-->
<select id="selectAllToMap" resultType="employee">
select *
from tbl_employee
</select>
測試:
Map<Integer, Employee> map = mapper.selectAllToMap();
System.out.println(map);
結果:
{1=Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1'}, 3=Employee{id=3, lastName='wangwu', email='wangwu@126.com', gender='0'}, 5=Employee{id=5, lastName='林青霞', email='lingqingxia@163.com', gender='1'}, 6=Employee{id=6, lastName='Tom', email='tom@126.com', gender='0'}}
而指定 resultType
屬性為實體類時,查詢到的結果會將指定的列映射到類的成員變量名上,結果映射到成員變量值上。
【注意】:實體類中的屬性名和數據庫中的字段名保持一致。否則結果值會為null。
如果列名和屬性名不能匹配上,且不滿足駝峰命名自動映射,可以在 SELECT 語句中設置列別名,也可以顯式配置 ResultMap
04 自定義ResultMap
如果列名和屬性名不能匹配上,在xml映射文件中顯式配置 ResultMap
標簽
- type:自定義規則的Java類型
- id:唯一id,用于引用
- column:指定數據庫中的字段
- property:指定對應的JavaBean中的屬性
<!--自定義結果集規則-->
<!--type:自定義規則的Java類型-->
<!--id:唯一id,用于引用-->
<resultMap id="MyEmp" type="employee">
<!--用id標簽定義主鍵底層會有優化,普通列用result標簽-->
<!--column:指定哪一列-->
<!--property:指定對應的JavaBean屬性-->
<!--不指定的列會自動封裝-->
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</resultMap>
<!--用resultMap取代resultType,值為自命名的id-->
<select id="selectEmp" resultMap="MyEmp">
select *
from tbl_employee
where id = #{id}
</select>
6. association–多對一查詢
處理多表查詢。
MyBatis 有兩種不同的方式加載關聯:
- 嵌套 Select 查詢:通過執行另外一個 SQL 映射語句來加載期望的復雜類型。
- 嵌套結果映射:使用嵌套的結果映射來處理連接結果的重復子集。
6.1 測試環境
兩張表,員工表tbl_employee和部門表tbl_dept,員工表中設置外鍵部門id,指向部門表的主鍵
在之前的基礎上新建了部門表,并在員工表中設置外鍵,插入數據:
CREATE TABLE tbl_dept(
id INT(10) PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(255)
)ENGINE=INNODB DEFAULT CHARSET=UTF8;
ALTER TABLE tbl_employee ADD COLUMN d_id INT(10);
ALTER TABLE tbl_employee ADD CONSTRAINT fk_emp_dept FOREIGN KEY(d_id) REFERENCES tbl_dept(id);
員工表:
部門表:
Employee實體類:
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
private Dept dept;
//getter/setter...
}
Dept實體類:
public class Dept {
private Integer id;
private String departmentName;
//getter/setter...
}
6.2 級聯屬性封裝結果
在接口文件中聲明一個根據員工id查詢員工信息的方法,因為員工信息中一個成員是部門對象,按照之前的映射關系,無法查詢部門信息。
public interface EmployeeMapper {
//根據id查詢員工的信息及其部門信息
Employee getEmpAndDept(Integer id);
}
采用級聯屬性來封裝結果:
employee中有一個成員是dept,由dept.id和dept.departmentName則可以獲取到部門的id和部門名稱
<!--聯合查詢,級聯屬性封裝結果集-->
<resultMap id="MyDifEmp" type="com.xiao.pojo.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<result column="did" property="dept.id"/>
<result column="dept_name" property="dept.departmentName"/>
</resultMap>
<select id="getEmpAndDept" resultMap="MyDifEmp ">
<!--給列起別名-->
select e.id id, e.last_name last_name,e.gender gender,e.email email,
d.id did,d.dept_name dept_name from tbl_employee e,tbl_dept d
where e.d_id = d.id and e.id = #{id}
</select>
測試:
Employee employee = mapper.getEmpAndDept(1);
System.out.println(employee);
結果:重點是執行的sql語句
DEBUG 06-12 20:16:45,106 ==> Preparing: select e.id id, e.last_name last_name,e.gender gender,e.email email, d.id did,d.dept_name dept_name from tbl_employee e,tbl_dept d where e.d_id = d.id and e.id = ? (BaseJdbcLogger.java:143)
DEBUG 06-12 20:16:45,213 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:143)
DEBUG 06-12 20:16:45,253 <== Total: 1 (BaseJdbcLogger.java:143)
Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1', dept=Dept{id=1, departmentName='開發部'}}
6.3 association嵌套結果集
關聯(association)元素可以處理多表查詢。
MyBatis 有兩種不同的方式加載關聯:
- 嵌套結果映射:使用嵌套的結果映射來處理連接結果的重復子集。
- 嵌套 Select 查詢:即分布查詢,通過執行另外一個 SQL 映射語句來加載期望的復雜類型。
查詢結果本質上是Employee類,其中的成員Dept是對象,需要進行關聯association,用javaType獲取屬性的類型。兩種方式結果相同。
<!--查詢結果本質上是Employee類-->
<resultMap id="MyDifEmp" type="com.xiao.pojo.Employee">
<!--對查詢結果進行映射-->
<!--數據庫中的字段取了別名后用別名-->
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- dept成員是一個對象,需要用association指定聯合的JavaBean對象-->
<!-- property="dept":指定哪個屬性是聯合的對象-->
<!-- javaType:指定這個屬性對象的類型,不能省略-->
<association property="dept" javaType="com.xiao.pojo.Dept">
<id column="did" property="id"/>
<id column="dept_name" property="departmentName"/>
</association>
</resultMap>
<select id="getEmpAndDept" resultMap="MyDifEmp">
<!--給列起別名-->
select e.id id, e.last_name last_name,e.gender gender,e.email email,
d.id did,d.dept_name dept_name from tbl_employee e,tbl_dept d
where e.d_id = d.id and e.id = #{id}
</select>
6.4 association分步查詢
思路:
- 先根據輸入的員工id查詢員工信息
- 再根據查詢到的員工信息中的d_id,查詢對應的部門信息
- 將部門信息設置給員工
<!--association分步查詢-->
<!--第一步,先查詢員工信息-->
<!--第二步,根據查詢到的員工信息的did,尋找對應的部門信息-->
<select id="getEmpAndDept" resultMap="MyDifEmp">
<!--第一步,先查詢員工信息-->
select id, last_name, gender,email,d_id from tbl_employee where id = #{id}
</select>
<resultMap id="MyDifEmp" type="com.xiao.pojo.Employee">
<!--簡單的屬性直接寫-->
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!--復雜的屬性,需要單獨處理-->
<!--select:調用指定的方法查出結果-->
<!--column:將哪一列的值傳給這個方法-->
<!--property:查出的結果封裝給property指定的屬性-->
<association property="dept" column="d_id" javaType="com.xiao.pojo.Dept" select="getDept"/>
</resultMap>
<!--第二步,根據查詢到的員工信息中的d_id,查詢對應的部門信息-->
<!--可以寫到部門的sql映射文件中,這里簡略了-->
<select id="getDept" resultType="com.xiao.pojo.Dept">
select id,dept_name departmentName from tbl_dept where id = #{d_id}
</select>
association標簽中的屬性:
- select:調用指定的方法查出結果
- column:將哪一列的值傳給這個方法
- property:查出的結果封裝給property指定的屬性
執行流程:使用select指定的方法,用傳入的column指定的這列參數的值,查出對象,并封裝給property屬性
結果:有兩條sql語句
DEBUG 06-12 20:04:49,831 ==> Preparing: select id, last_name, gender,email,d_id from tbl_employee where id = ? (BaseJdbcLogger.java:143)
DEBUG 06-12 20:04:49,945 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:143)
DEBUG 06-12 20:04:50,006 ====> Preparing: select id,dept_name departmentName from tbl_dept where id = ? (BaseJdbcLogger.java:143)
DEBUG 06-12 20:04:50,008 ====> Parameters: 1(Integer) (BaseJdbcLogger.java:143)
DEBUG 06-12 20:04:50,015 <==== Total: 1 (BaseJdbcLogger.java:143)
DEBUG 06-12 20:04:50,017 <== Total: 1 (BaseJdbcLogger.java:143)
Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1', dept=Dept{id=1, departmentName='開發部'}}
分步查詢可以使用延遲加載
延遲加載(懶加載、按需加載),需要在全局配置文件中開啟
- lazyLoadingEnabled:延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 特定關聯關系中可通過設置
fetchType
屬性來覆蓋該項的開關狀態。默認是false - aggressiveLazyLoading:開啟時,任一方法的調用都會加載該對象的所有延遲加載屬性。 否則,每個延遲加載屬性會按需加載。(在 3.4.1 及之前的版本中默認為 true,之后默認為false)
<settings>
<!--開啟懶加載-->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
測試中只使用查詢到的員工信息,不使用員工信息,則只會發出一條sql語句,如果不開啟延遲加載,則總是會發出兩條sql語句:
Employee employee = mapper.getEmpAndDept(1);
System.out.println(employee.getEmail());
結果,只發出了第一步的sql語句:
DEBUG 06-12 20:31:55,182 ==> Preparing: select id, last_name, gender,email,d_id from tbl_employee where id = ? (BaseJdbcLogger.java:143)
DEBUG 06-12 20:31:55,290 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:143)
DEBUG 06-12 20:31:55,516 <== Total: 1 (BaseJdbcLogger.java:143)
zhangmanyu@163.com
7 collection–一對多查詢
查詢部門,并講部門對應的所有員工信息查詢出來
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
//getter/setter...
}
Dept實體類:
public class Dept {
private Integer id;
private String departmentName;
private List<Employee> emps;
//getter/setter...
}
DeptMapper接口文件:
public interface DeptMapper {
Dept selectDeptById(Integer id);
}
7.1 嵌套結果集
使用關聯查詢,根據Dept的id查詢Dept信息,其中Dept中有一個成員是emps,即員工的集合,需要用到collection標簽進行結果映射。
集合標簽:collection
- property:指定哪個屬性是集合
- ofType:獲取集合中的泛型信息
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="com.xiao.dao.DeptMapper">
<!--嵌套結果查詢-->
<select id="selectDeptById" resultMap="MyDept">
select d.id did, d.dept_name dept_name, e.id eid, e.last_name last_name, e.email email, e.gender gender
from tbl_dept d
left join tbl_employee e
on d.id = e.d_id
where d.id = #{id}
</select>
<resultMap id="MyDept" type="com.xiao.pojo.Dept">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
<!--復雜的屬性,單獨處理,集合:collection,property指定哪個屬性是集合,用ofType獲取集合中的泛型信息-->
<collection property="emps" ofType="com.xiao.pojo.Employee">
<result column="eid" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
</mapper>
測試:
Dept dept = mapper.selectDeptById(1);
System.out.println(dept);
查詢結果,一個部門中有兩個員工信息:
DEBUG 06-12 21:24:02,595 ==> Preparing: select d.id did, d.dept_name dept_name, e.id eid, e.last_name last_name, e.email email, e.gender gender from tbl_dept d left join tbl_employee e on d.id = e.d_id where d.id = ? (BaseJdbcLogger.java:143)
DEBUG 06-12 21:24:02,675 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:143)
DEBUG 06-12 21:24:02,721 <== Total: 2 (BaseJdbcLogger.java:143)
Dept{id=1, departmentName='開發部', emps=[Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1'}, Employee{id=3, lastName='wangwu', email='wangwu@126.com', gender='0'}]}
7.2 分步查詢
思路與多對一中的思路類型:
- 先根據輸入的部門id查詢部門信息
- 再根據查詢到的部門信息中的部門id,查詢對應的員工信息
- 將員工信息設置給部門
<!--分步查詢-->
<select id="selectDeptById" resultMap="MyDept">
select id, dept_name
from tbl_dept
where id = #{id}
</select>
<resultMap id="MyDept" type="com.xiao.pojo.Dept">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<collection property="emps" ofType="com.xiao.pojo.Employee" select="getEmp" column="id"/>
</resultMap>
<select id="getEmp" resultType="com.xiao.pojo.Employee">
select id,last_name lastName,email,gender from tbl_employee where d_id = #{id}
</select>
7.3 補充幾個小點
-
分步查詢如何傳遞多列的值?
將多列的值封裝成map傳遞:column={key1=column1,key2=column2…},使用時則用#{key}獲取 -
開啟了全局懶加載后,特定關聯關系中可通過設置
fetchType
屬性來覆蓋該項的開關狀態- fetchType=“lazy”:懶加載
- fetchType=“eager”:立即加載
-
鑒別器discriminator
類似于 Java 語言中的 switch 語句,可以根據不同的條件封裝不同的結果集。需要指定 column 和 javaType 屬性。- javaType:列值對應的java類型
- column:指定用于判定的列名
- value:進行條件的判斷,滿足該條件,則選擇該結果封裝規則
<discriminator javaType="string" column="gender">
<case value="0" resultType="com.xiao.pojo.Employee">
....
</case>
<case value="0" resultType="com.xiao.pojo.Employee">
....
</case>
</discriminator>
智能推薦
Mybatis學習筆記(一)
1、環境描述 java版本:1.8 開發工具:idea 2018版 mysql:8.x版本 mybatis:3.4.6版本 lombok mybatis 在開發中最為關鍵的是其 SqlSession 的使用和 mapper 文件的配置。 本文將圍繞它來介紹Mybatis的入門案例。 2、項目結構 2.1 配置文件 在 idea 中,使用maven項目。在 resources 資源文件夾中創建文件夾...
MyBatis學習筆記(一)
一、MyBatis介紹 MyBatis 是一款優秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記...
Mybatis學習筆記一
Mybatis學習筆記一 概述 Mybatis 是一個優秀的基于 java 的持久層框架,它內部封裝了 jdbc,使開發者只需要關注 sql 語句本身,而不需要花費精力去處理加載驅動、創建連接、創建 statement 等繁雜的過程。 mybatis 通過 xml 或注解的方式將要執行的各種 statement 配置起來,并通過 java 對象和 statement 中sql 的動態參數進行映射生...
MyBatis學習筆記(一)
1 、對原生態jdbc程序中問題總結 jdbc編程步驟: 1、 加載數據庫驅動 2、 創建并獲取數據庫鏈接 3、 創建jdbc statement對象 4、 設置sql語句 5、 設置sql語句中的參數(使用preparedStatement) 6、 通過statement執行sql并獲取結果 7、 對sql執行結...
Mybatis學習筆記(一)
嗷嗷 環境統一 JDK1.8 Mysql5.7 maven3.6.1 簡介 什么是mybatis MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,并且改名為MyBatis 。2013年11月遷移到Github。 iBATIS一詞來源于“internet”和...
猜你喜歡
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...