MyBatis_01
1. MyBatis 認(rèn)識
MyBatis 是 ORM 的一種實現(xiàn); 使用MyBatis可以簡化JDBC的操作, 實現(xiàn)數(shù)據(jù)的持久化, 代替的就是三層模型種的Dao層
ORM : Object Relational Mapping : 對象關(guān)系映射; MyBatis的實現(xiàn)就是將數(shù)據(jù)庫種的一張表映射成一個JavaBeen對象; 通過 ORM 就可以像操作對象一樣操作數(shù)據(jù)庫種對應(yīng)的表
2. 配置 MyBatis 及測試
下載 MyBatis3.4.6
下載后需要將 mybatis3.4.6.jar 引入需要使用的項目中
MyBatis 是實現(xiàn)數(shù)據(jù)庫表和JavaBeen的映射關(guān)系, 這種映射關(guān)系的實現(xiàn)就是每個JavaBeenMapper.xml; 在映射文件中通過標(biāo)簽定義的增刪改查就會實際作用到數(shù)據(jù)庫表中
-
如: personMapper.xml示例及參數(shù)說明
namespace: 該javaBeenMapper.xml 映射文件的全限定路徑; (在面向接口開發(fā)中: namespace的值就是對應(yīng)接口的全限定類名)
id : SQL語句的標(biāo)簽
-
parameterType : 接收參數(shù)的類型
輸入?yún)?shù)parameterType和輸出參數(shù)resultType, 在形式上只能有一個(即如果需要傳遞多個參數(shù), 就需要將多個參數(shù)封裝起來)
如果輸入/輸出參數(shù)是簡單類型(8個基本類型+String): 則在#{}中可以使用任何占位符; 如果是對象類型, 則#{}中必須是對象的屬性
-
resultType : 結(jié)果的類型, 即查完返回值類型;
- 如果返回值類型是一個對象(如: Student), 則無論返回一個, 還是返回多個, 在resultType中都寫成 "xyz.xmcs.Entity.Student" 的形式
<!-- studentMapper.xml: 用于映射 Student.java 和 MySQL的student表 -->
<?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="xyz.xmcs.Entity.studentMapper">
<!-- id: 該標(biāo)簽的唯一標(biāo)識; resultType:結(jié)果的類型, 即查完返回值類型; parameterType:接收參數(shù)的類型 -->
<!-- #{} 就相當(dāng)于 pstmt 中SQL語句里面的 ? -->
<select id="queryStudentByStuNo" resultType="xyz.xmcs.Entity.Student" parameterType="int">
select * from student where stuNo = #{stuNo}
</select>
<!-- 增加 -->
<insert id="addStudent" parameterType="xyz.xmcs.Entity.Student" >
insert into student(stuNo, stuName, stuAge, graName) values(#{stuNo}, #{stuName}, #{stuAge}, #{graName})
</insert>
<!-- 刪除 -->
<delete id="deleteStudentByStuNo" parameterType="int">
delete from student where stuNo = #{stuNo}
</delete>
<!-- 修改 -->
<update id="updateStudentByStuNo" parameterType="xyz.xmcs.Entity.Student">
update student set stuName=#{stuName}, stuAge=#{stuAge}, graName=#{graName} where stuNo=#{stuNo}
</update>
<!-- 查詢?nèi)?-->
<select id="queryAllStudent" resultType="xyz.xmcs.Entity.Student">
select * from student
</select>
</mapper>
- 除上述實現(xiàn)映射的配置外, 還需要配置數(shù)據(jù)庫信息以及需要加載的映射文件; 即MyBatis的總配置文件; 在src同級目錄下配置 conf.xml; conf.xml 重要標(biāo)簽說明
conf.xml 重要標(biāo)簽說明
configuration : 配置信息區(qū)域, 所有的配置信息卸載 configuration 標(biāo)簽體中
properties : 用于引入動態(tài)文件, 通過 resource 指定文件名來引入
settings : 用于設(shè)置全局參數(shù); 可設(shè)置 二級緩存, 開啟日志, 延遲加載, 以及其他配置
typeAliases : 用于設(shè)置單個/多個別名
typeHandlers : 用于配置類型轉(zhuǎn)換器, (JDBC類型和JAVA類型之間的轉(zhuǎn)換)
plugins : 通過<\plugin>標(biāo)簽配置自定義的插件
-
environments : 用于環(huán)境配置, environments里面可以配置多個environment, 并且在environment中的id指明數(shù)據(jù)庫環(huán)境, 在environments里的default中選擇對應(yīng)的id來指定MyBatis運行時的數(shù)據(jù)庫環(huán)境; (也可以在SSqlSessionFactoryBuilder().build(reader, "environment的id")來強行指定運行時的環(huán)境)
-
environment: 具體的環(huán)境配置, 可配置多個
-
dataSource: (在environment中配置)指明environment使用的數(shù)據(jù)源類型, 有如下三種類型, 一般選擇 POOLED
POOLED: 使用數(shù)據(jù)庫連接池
UNPOOLED: 使用傳統(tǒng)的JDBC模式(每次打開關(guān)閉, 消耗性能)
JNDI: 從tomcat里面獲取一個內(nèi)置的數(shù)據(jù)庫連接池
dataSource內(nèi)的property數(shù)據(jù)庫連接用的信息, 可以直接寫在conf.xml 中, 也可以將信息用KV對的形式寫在properties配置文件中, 在configuration標(biāo)簽里面增加 properties標(biāo)簽并指定其resource屬性來引入配置文件, 在 property 的值中就可以通過類似于EL表達式的方式來引入
-
-
transactionManager: (在environment中配置)指明事務(wù)的提交方式, 有如下兩種, 一般選擇 JDBC
-
JDBC : 利用JDBC方式處理事務(wù)(需要手工的 commit, rollback, close)
- 手工提交就是在代碼中通過SqlSession的connit()方法手工提交
MANAGED : 將事務(wù)交由其他組件去托管(如: spring, jobss), 默認(rèn)會關(guān)閉連接, 不關(guān)閉的話需要在transactionManager下面配置: (<property name="closeConnection" value="false" />)
-
-
databaseIdProvider : 配置數(shù)據(jù)庫支持類: 大小寫不能亂寫; 通過 type 來指定, 一般是 DB_VENDOR; 標(biāo)簽體內(nèi)部通過property指定數(shù)據(jù)庫
mappers : 用于指定映射文件, 可以單獨映射, 也可以通過package批量映射
-
注意的一個細節(jié)
使用mysql8后, URL信息在xml和properties文件中需要注意(&)的細微區(qū)別; xml中(&)應(yīng)該寫成& 而在properties中可以直接寫 &
URL在properties中, 后面要增加一句: &allowPublicKeyRetrieval=true 否則MyBatis會報錯
如 conf.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>
<!-- 引人動態(tài)文件 -->
<properties resource="db.properties" />
<!-- 設(shè)置全局參數(shù)-->
<settings>
<!-- true: 開啟二級緩存 false: 關(guān)閉二級緩存; 再具體的Mapper.xml 中需要通過<cache/>標(biāo)簽聲明-->
<setting name="cacheEnabled" value="true" />
<!--<setting name="lazyLoadingEnabled" value="false" />-->
<!-- 開啟日志, 并指定使用的具體日志 -->
<setting name="logImpl" value="LOG4J"/>
<!-- 開啟延遲加載 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 關(guān)閉立即加載 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 上面的配置沒錯, 但是運行就報 Error creating lazy proxy. Cause: java.lang.NullPointerException
的異常, 看了網(wǎng)上的解決方案是將 MyBatis的jar包更新到3.5.1
-->
<!-- 配置全局的NULL, 在遇到數(shù)據(jù)庫(oracle)不能解析時, 就使用NULL -->
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
<!-- 設(shè)置單個/多個別名 -->
<typeAliases>
<!-- 單個別名
<typeAlias type="xyz.xmcs.Entity.Student" alias="Student" /> -->
<!-- 批量設(shè)置別名 -->
<package name="xyz.xmcs.Entity" />
</typeAliases>
<!-- 配置類型轉(zhuǎn)換器: JDBC類型和JAVA類型之間的轉(zhuǎn)換 -->
<typeHandlers>
<!-- 指定轉(zhuǎn)換器的全類名, 并且說明java 和 jdbc 轉(zhuǎn)換的類型 -->
<typeHandler handler="xyz.xmcs.converter.BooleanAndIntConverter" javaType="Boolean" jdbcType="INTEGER"/>
</typeHandlers>
<!-- 配置插件: 在這里配置自定義的插件 -->
<!--<plugins>-->
<!--
<plugin interceptor="xyz.xmcs.my.interceptors.MyInterceptor">
<property name="name" value="zs" />
<property name="age" value="23"/>
</plugin>
-->
<!--
<plugin interceptor="xyz.xmcs.my.interceptors.MyInterceptor2">
<property name="name" value="zs22222" />
<property name="age" value="33"/>
</plugin>
-->
<!--</plugins>-->
<!-- 該標(biāo)簽中可配置數(shù)據(jù)庫信息, 如連接oracle, mysql 等 -->
<environments default="devMySQL">
<!-- MySQL 數(shù)據(jù)庫 -->
<environment id="devMySQL">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 配置數(shù)據(jù)庫信息; 可以直接在value中寫死, 也可以將值以kv對的形式寫在properties文件中,
以類似于EL表法是的方式動態(tài)引入(需要先加入 properties標(biāo)簽, 引入properties文件)-->
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
<!-- Oracle 數(shù)據(jù)庫 -->
<environment id="devOracle">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 配置數(shù)據(jù)庫信息; 可以直接在value中寫死, 也可以將值以kv對的形式寫在properties文件中,
以類似于EL表法是的方式動態(tài)引入(需要先加入 properties標(biāo)簽, 引入properties文件)-->
<property name="driver" value="${oracle.driver}"/>
<property name="url" value="${oracle.url}"/>
<property name="username" value="${oracle.username}"/>
<property name="password" value="${oracle.password}"/>
</dataSource>
</environment>
</environments>
<!-- 配置數(shù)據(jù)庫支持類: 大小寫必能亂寫 -->
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<!--<property name="SQL Server" value=""/>-->
</databaseIdProvider>
<mappers>
<!-- 加載映射文件 -->
<mapper resource="xyz/xmcs/Entity/personMapper.xml"/>
<!-- 這種是批量引入mapper; 可以將 xyz.xmcs.mapper包中的注解接口(即接口中的方法上帶注解)和xml全部一次性引入-->
<package name="xyz.xmcs.Mapper"/>
<!-- 這是使用注解的方式, 并單獨引入; 不推薦使用
<mapper class="xyz.xmcs.Mapper.StudentMapper"/>-->
<!--
<mapper resource="xyz/xmcs/Mapper/studentMapper.xml"/>
<mapper resource="xyz/xmcs/Mapper/studentCardMapper.xml"/>
<mapper resource="xyz/xmcs/Mapper/studentClassMapper.xml"/> -->
</mappers>
</configuration>
實現(xiàn)MyBatis基礎(chǔ)方式的增刪改查
-
測試類和傳統(tǒng)的JDBC一樣, 也有固定的步驟:
加載MyBatis配置文件(就是為了訪問數(shù)據(jù)庫): 通過Resources.getResourceAsReader("conf.xml")獲取配置文件的輸入流對象
通過new SqlSessionFactoryBuilder().build("輸入流對象") 獲得 SqlSession 對象; SqlSession對象就相當(dāng)于Connection
通過獲得的SqlSession對象調(diào)用其方法實現(xiàn)查詢并返回結(jié)果: sqlSession.selectOne("需要查詢的SQL的namespace.id", "SQL的參數(shù)值");
手動提交事務(wù): sqlSession.commit();
關(guān)閉 SqlSession 對象: sqlSession.close();
示例:
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 java.io.IOException;
import java.io.Reader;
import java.util.List;
public class TestStudent {
// 提取獲得SqlSession的工具類
private static SqlSession getSqlSession() throws IOException {
// 1. 加載MyBatis配置文件為Reader對象
Reader reader = Resources.getResourceAsReader("conf.xml");
// 2. 通過 Reader 對象獲取SqlSessionFactory 對象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 3. 通過 SqlSessionFactory的對象獲取SqlSession, 并通過調(diào)用SqlSession對象的方法實現(xiàn)數(shù)據(jù)庫操作并拿到操作結(jié)果
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
// 按stuNo查詢一個學(xué)生
public static void queryStudentByStuNo() throws IOException {
SqlSession sqlSession = getSqlSession();
String stmt = "xyz.xmcs.Entity.studentMapper.queryStudentByStuNo";
Student student = sqlSession.selectOne(stmt,1);
System.out.println(student);
// 4. 手動提交事務(wù)
sqlSession.commit();
// 5. 關(guān)閉SqlSession對象
sqlSession.close();
}
// 增加學(xué)生
public static void addStudent() throws IOException {
SqlSession sqlSession = getSqlSession();
String stmt = "xyz.xmcs.Entity.studentMapper.addStudent";
Student student = new Student(2, "ls", 20, "class1");
int insert = sqlSession.insert(stmt, student);
sqlSession.commit();
System.out.println("Insert Result:" + insert);
sqlSession.close();
}
// 刪除學(xué)生
public static void deleteStudent() throws IOException {
SqlSession sqlSession = getSqlSession();
String stmt = "xyz.xmcs.Entity.studentMapper.deleteStudentByStuNo";
int delete = sqlSession.delete(stmt, 3);
System.out.println("Delete Result:" + delete);
sqlSession.commit();
sqlSession.close();
}
// 查詢?nèi)繉W(xué)生
public static void queryAllStudent() throws IOException {
SqlSession sqlSession = getSqlSession();
String stmt = "xyz.xmcs.Entity.studentMapper.queryAllStudent";
List<Student> students = sqlSession.selectList(stmt);
for (Student student : students) {
System.out.println(student);
}
sqlSession.commit();
sqlSession.close();
}
// 根據(jù) stuNo 修改學(xué)生
public static void updateStudentByStuNo() throws IOException {
SqlSession sqlSession = getSqlSession();
String stmt = "xyz.xmcs.Entity.studentMapper.updateStudentByStuNo";
Student student = new Student(2, "LS", 14, "class2");
int update = sqlSession.update(stmt, student);
System.out.println("Update Result:" + update);
sqlSession.commit();
sqlSession.close();
}
public static void main(String[] args) throws IOException {
queryStudentByStuNo();
addStudent();
queryAllStudent();
updateStudentByStuNo();
deleteStudent();
}
}
3. 實現(xiàn)mapper動態(tài)代理方式的CRUD (MyBatis接口開發(fā))
原則: 約定優(yōu)于配置
-
配置方式: 就是在一個 xxxMapper.xml 文件中指定配置參數(shù)
- 如: 在 xxxMapper.xml 中指定: <name>MyBatisTest</name>
硬編碼方式: 就是在xxx.java中: 通過 new Configuration().setName("MyBatisTest")
具體實現(xiàn)步驟
基礎(chǔ)環(huán)境: mybatis.jar, mysql-connection.jar, conf.xml, mapper.xml 都和上述基礎(chǔ)查詢時配置的一樣
-
與基礎(chǔ)查詢的不同之處:
約定的目標(biāo): 省略掉stmt, 即根據(jù)約定直接可以定位處SQL語句
習(xí)慣將mapper.xml 和 接口放在同一個包中
-
約定:(即面向接口開發(fā), 約定就是對接口以及接口中方法的一些約定; 說到底就是讓接口和對應(yīng)的mapper.xml有關(guān)聯(lián))
mapper.xml 中namespace的值就是接口的全類名(即: 實現(xiàn)接口和mapper.xml的一一對應(yīng))
接口中的方法名和mapper.xml中SQL標(biāo)簽的id一致
方法的輸入?yún)?shù)和mapper.xml中parameterType的類型一致;(如果mapper.xml中沒有parameterType, 則說明沒有輸入?yún)?shù), 或者輸入多個參數(shù))
返回值和mapper.xml中的resultType類型一致; (如果mapper.xml中沒有resultType, 則說明沒有輸出參數(shù)(也可以在接口抽象方法中直接定義返回值類型))
-
匹配的過程: (約定的過程)
根據(jù) 接口名 找到 mapper.xml 文件; (根據(jù)的是namespace=接口的全類名)
根據(jù)接口的 方法名 找到mapper.xml文件中的SQL標(biāo)簽; (根據(jù)的是: 接口方法名=SQL標(biāo)簽的Id值)
基于以上兩種約定: 當(dāng)我們調(diào)用接口中的方法時, 程序能自動定位到某一個mapper.xml文件中的SQL標(biāo)簽
-
面向接口的執(zhí)行步驟:
獲取 SqlSession 對象
調(diào)用 SqlSession 對象的 getMappr(接口.class) 方法獲取接口, 返回值類型就是接口類型
再通過調(diào)用該接口的對應(yīng)的方法來執(zhí)行即可完成SQLCRUD操作
手動commit, 并關(guān)閉SqlSession對象
4. MyBatis 優(yōu)化
可以將配置信息單獨放入 properties 文件中, 然后動態(tài)引入
在 properties 中以 K=V 的形式保存配置信息
在 <\configuration>標(biāo)簽中通過 <\properties resource="db.properties" />來引入配置文件
在需要引用的地方通過: ${k} 即可引入需要的V值
設(shè)置MyBatis全局參數(shù)
在conf.xml 中的<\configuration>標(biāo)簽中通過<\settings>標(biāo)簽來設(shè)定
如:
<settings>
<setting name="cacheEnabled" value="false" />
<setting name="lazyLoadingEnabled" value="false" />
</settings>
設(shè)置別名
設(shè)置別名主要是解決一個全類名很長的問題, 通過設(shè)置別名, 就可以使用一個別名來代替全類名; 并且不區(qū)分大小寫
單個別名還是批量設(shè)置別名, 都是在 conf.xml 中的 <\configuration> 標(biāo)簽中通過 <\typeAliases> 標(biāo)簽來設(shè)置的
設(shè)置單個別名: 就是將一個類單獨起一個別名, 且別名是忽略大小寫的
批量設(shè)置別名: 就是將包下的所有類批量設(shè)置別名, 別名就是類名(不帶包名), 也是忽略大小寫的
<!-- 設(shè)置單個/多個別名 -->
<typeAliases>
<!-- 單個別名
<typeAlias type="xyz.xmcs.Entity.Student" alias="Student" /> -->
<!-- 批量設(shè)置別名 -->
<package name="xyz.xmcs.Entity" />
</typeAliases>
MyBatis自帶的別名
_byte=byte _long=long _short=short _int-int _integer=int long=Long int=Integer integer=Integer boolean=Boolean
decimal=BigDecimal object=Object hashmap=HashMap arraylist=ArrayList iterator=Iterator _double=double _float=float
_boolean=boolean string=String byte=Byte short=Short double=Double float=Float date=Date bigdecimal=BigDecimal
map=Map list=List collection=Collection
5. MyBatis類型處理器(類型轉(zhuǎn)換器)
- MyBatis 自帶一些常見的類型處理器
自定義MyBatis類型處理器
類型轉(zhuǎn)換就是Java代碼和JDBC類型之間的轉(zhuǎn)換
以例子來學(xué)習(xí): 如Java實體類Student.java里true代表男生, false代表女生; 而在student表中以int類型來代表男女: 1代表男生, 0代表女生; 而需要做的就是實現(xiàn)讓true轉(zhuǎn)換為1, 而將false轉(zhuǎn)換為0
-
自定義類型轉(zhuǎn)換器(將 boolean --> int)步驟
創(chuàng)建轉(zhuǎn)換器(需要實現(xiàn)TypeHandler接口或者繼承TypeHandler的實現(xiàn)類BaseTypeHandler)
在conf.xml中的configuration標(biāo)簽里面的typeHandlers標(biāo)簽中配置
示例
// BooleanAndIntConverter.java 實現(xiàn)的轉(zhuǎn)換器將JAVA的Boolean轉(zhuǎn)換為JDBC的int
package xyz.xmcs.converter;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class BooleanAndIntConverter extends BaseTypeHandler<Boolean> {
// java(boolean) --> DB(int)
/**
* @param preparedStatement: preparedStatement對象
* @param i : preparedStatement對象操作參數(shù)的位置
* @param aBoolean: java的值
* @param jdbcType : JDBC操作的數(shù)據(jù)庫類型
* @throws SQLException
*/
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Boolean aBoolean, JdbcType jdbcType) throws SQLException {
// 思路: 就是判斷 aBoolean 是true還是false; 如果true, 就通過preparedStatement的setInt()方法將i位置設(shè)為1; 否則設(shè)為0
if(aBoolean) {
// 1
preparedStatement.setInt(i, 1);
} else {
// 0
preparedStatement.setInt(i, 0);
}
}
// DB(int) --> java(boolean) 按照列名拿值
@Override
public Boolean getNullableResult(ResultSet resultSet, String s) throws SQLException {
int sexNum = resultSet.getInt(s);
return sexNum == 1? true : false;
}
// DB(int) --> java(boolean) 按列數(shù)拿值
@Override
public Boolean getNullableResult(ResultSet resultSet, int i) throws SQLException {
int sexNum = resultSet.getInt(i);
return sexNum == 1? true : false;
}
// DB(int) --> java(boolean) 存儲過程或存儲函數(shù)拿值
@Override
public Boolean getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
int sexNum = callableStatement.getInt(i);
return sexNum == 1 ? true : false;
}
}
<!-- conf.xml:配置轉(zhuǎn)換器 -->
<typeHandlers>
<!-- 指定轉(zhuǎn)換器的全類名, 并且說明java 和 jdbc 轉(zhuǎn)換的類型
需要注意: jdbcType類型 需要全大寫 INTEGER
-->
<typeHandler handler="xyz.xmcs.converter.BooleanAndIntConverter" javaType="Boolean" jdbcType="INTEGER"/>
</typeHandlers>
測試自定義類型轉(zhuǎn)換
在寫好自定義類型轉(zhuǎn)換類以及在conf.xml配置的基礎(chǔ)上測試自定義類型轉(zhuǎn)換
在MySQL數(shù)據(jù)庫MyBatisTest的 student 表中增加 stuSex 列來記錄學(xué)生的性別: alter table student add stuSex int(2);
在 Entity 包下的Student.java 中增加 boolean stuSex 屬性, 并增加setter和getter以及修改構(gòu)造器
-
查詢學(xué)生的SQL標(biāo)簽來測試自定義類型轉(zhuǎn)換
在 StudentMapper.xml 中增加id=queryStudentByStuNoWithConverter的 select標(biāo)簽; 需要注意的是接收的返回值需要(int --> boolean)類型轉(zhuǎn)換, 所以不能使用resultType而是使用resultMap; 并且需要單獨定義 <\resultMap> 標(biāo)簽; 并且在 resultMap標(biāo)簽許需要轉(zhuǎn)換的標(biāo)簽中添加 jdbcType 和 javaType; 然后在queryStudentByStuNoWithConverter 中指定此 resultMap 的 id 即可
在 StudentMapper.java 接口中增加方法:
Student queryStudentByStuNoWithConverter(int stuNo);
在測試類中實現(xiàn)此方法來測試
-
增加學(xué)生的SQL標(biāo)簽來測試自定義類型轉(zhuǎn)換
在 StudentMapper.xml 中增加id=addStudentWithConverter的 insert標(biāo)簽, 只是在 sql語句中需要類型轉(zhuǎn)換的參數(shù)后面增加 javaType=java類型, jdbcType=INTEGER(JDBC類型)
在 StudentMapper.java 接口中增加方法:
void addStudentWithConverter(Student student);
在測試類中實現(xiàn)此方法來測試
測試代碼示例
- StudentMapper.xml 中增加的標(biāo)簽
<resultMap type="Student" id="studentResult">
<!-- 分為主鍵和非主鍵: id 指定主鍵, result 指定非主鍵 -->
<id property="stuNo" column="stuNo"/>
<result property="stuName" column="stuName"/>
<result property="stuAge" column="stuAge"/>
<result property="graName" column="graName"/>
<!-- 在需要類型轉(zhuǎn)換的地方, 通過 jdcbType 和 JavaType 來指定類型 -->
<result property="stuSex" column="stuSex" jdbcType="INTEGER" javaType="boolean"/>
</resultMap>
<!-- 查詢: 使用了類型轉(zhuǎn)換器
如果類中的屬性和表中的字段類型能夠合理識別(String - varchar), 則可以使用resultType; 否則(boolean - int)使用resultMap
如果類中的屬性名和表中的字段名能夠一一對應(yīng)(stuNo - stuno), 則可以使用resultType; 否則(stuNo - id)使用resultMap
-->
<select id="queryStudentByStuNoWithConverter" resultMap="studentResult" parameterType="int">
select * from student where stuNo = #{stuNo}
</select>
<!-- 增加: 帶類型轉(zhuǎn)換 -->
<insert id="addStudentWithConverter" parameterType="Student" >
insert into student(stuNo, stuName, stuAge, graName, stuSex) values(#{stuNo}, #{stuName}, #{stuAge}, #{graName}, #{stuSex, javaType=boolean, jdbcType=INTEGER})
</insert>
- StudentMapper.java 接口中新增的方法
// 查詢一個學(xué)生, 使用了類型轉(zhuǎn)換
Student queryStudentByStuNoWithConverter(int stuNo);
// 增加學(xué)生 帶類型轉(zhuǎn)換
void addStudentWithConverter(Student student);
- TestStudent.java 中的測試方法
// 測試類型轉(zhuǎn)換查詢
public static Student queryStudentByStuNoWithConverter(int stuNo) throws IOException {
SqlSession sqlSession = getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.queryStudentByStuNoWithConverter(stuNo);
System.out.println(student);
sqlSession.commit();
sqlSession.close();
return student;
}
// 測試類型轉(zhuǎn)換增加
public static void addStudentWithConverter() throws IOException {
SqlSession sqlSession = getSqlSession();
Student student1 = new Student(3, "ju", 20, "class3", false);
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
mapper.addStudentWithConverter(student1);
sqlSession.commit();
sqlSession.close();
}
SQL 標(biāo)簽中 resultType 和 resultMap 的區(qū)別
如果類中的屬性和表中的字段類型能夠合理識別(String - varchar), 則可以使用resultType; 否則(boolean - int)使用resultMap
如果類中的屬性名和表中的字段名能夠一一對應(yīng)(stuNo - stuno), 則可以使用resultType; 否則(stuNo - id)使用resultMap
總結(jié): resultMap 可以實現(xiàn)類型轉(zhuǎn)換和 屬性與字段的映射關(guān)系兩個功能
resultMap 標(biāo)簽示例
<resultMap type="Student" id="studentResult">
<!-- 當(dāng)Been中的屬性名和表中的字段名不一致的時候, 就要通過下面這樣的方式來指定(property: Been中的屬性名, column:表中的字段名)
<id property="id" column="stuNo"/>
-->
<!-- 分為主鍵和非主鍵 -->
<id property="stuNo" column="stuNo"/>
<result property="stuName" column="stuName"/>
<result property="stuAge" column="stuAge"/>
<result property="graName" column="graName"/>
<!-- stuSex 需要類型轉(zhuǎn)換, 則需要在標(biāo)簽內(nèi)注明 -->
<result property="stuSex" column="stuSex" jdbcType="INTEGER" javaType="boolean"/>
</resultMap>
6. 輸入?yún)?shù): parameterType
parameterType 類型為簡單類型: (8個基本類型+String)
#{任意值} 中 {} 花括號里面在標(biāo)簽中可以是任意值; 但是在接受實參時 #{} 會自動給String類型加上 ('') 單引號(自動類型轉(zhuǎn)換; 這樣可以防止SQL注入)
${value} 中 {} 花括號的標(biāo)識符只能是 value; ${} 是原樣輸出(傳遞什么值就是什么值), 所以String字段的話, 需要手工加上單引號, 但是適合于動態(tài)排序(動態(tài)字段)
parameterType 類型為對象類型
- #{} 和 ${} 花括號里面只能是屬性名
#{} 和 ${} 的相同之處
-
都可以獲取對象的值, (甚至是嵌套類型的對象)
如使用like進行模糊查詢: 使用#{}時, 傳值時需要傳入"%值%"; 而使用${}時, 在配置時就配置成 '%${stuName}%', 所以傳值時可以直接傳遞字符串
如通過級聯(lián)屬性來查詢: 使用#{}時, 如 #{address.homeAddress} ; 使用${}時, 如 '${address.schoolAddress}'
parameterType 類型為 HashMap
原理就是用Map中的key匹配占位符#{}中的字段, (所以就要求key的值和占位符得到內(nèi)容要一致)
StudentMapper.xml 中只需要修改 parameterType 的類型為 HashMap
重寫相關(guān)的接口方法, 將接受參數(shù)類型改為 Map<Stirng, Object> 類型
在實際使用中就是創(chuàng)建Map實例, 添加需要的參數(shù), 參數(shù)的 key 就是 SQL語句中需要的字段, 而 value 就是需要查詢的字段
MyBatis 調(diào)用存儲過程(輸入?yún)?shù)HashMap)
- 在數(shù)據(jù)庫編寫兩個存儲過程
-- 查詢某個年級的學(xué)生總數(shù)
delimiter $ -- 將結(jié)束符改成$
CREATE procedure queryCountByGradeWithProcedure(OUT sCount int(10), IN gName varchar(255))
BEGIN
select count(1) INTO sCount from student where graName = gName;
END$
-- 根據(jù)學(xué)號刪除學(xué)生
CREATE procedure deleteStudentByStuNo(In stuno int(10))
begin
delete from student where stuNo = stuno;
end$
- 在 studentMapper.xml 中編寫 select 以及 delete 標(biāo)簽; 需要注意的是存儲過程的調(diào)用方法以及顯式的聲明statementType="CALLABLE"; 輸入?yún)?shù)使用Map傳遞
<!-- 調(diào)用[存儲過程]實現(xiàn)查詢 statementType默認(rèn)是STATEMENT, 如果是存儲過程, 就特別指定;
存儲過程的輸入?yún)?shù), 在MyBatis中用Map(HashMap)來傳遞
-->
<select id="queryCountByGradeWithProcedure" statementType="CALLABLE" parameterType="HashMap">
{call queryCountByGradeWithProcedure(#{gName, jdbcType=VARCHAR, mode=IN},#{sCount, jdbcType=INTEGER, mode=OUT})}
</select>
<delete id="deleteStudentByStuNoWithProcedure" parameterType="HashMap" statementType="CALLABLE">
{call deleteStudentByStuNo(#{stuno mode=IN jdbcType=INTEGER})}
</delete>
- 編寫StudentMapper.java 接口的方法
// 根據(jù)存儲過程查詢某個年級的學(xué)生總數(shù)
void queryCountByGradeWithProcedure(Map<String, Object> map);
// 根據(jù)存儲過刪除某個stuno學(xué)號的學(xué)生
void deleteStudentByStuNoWithProcedure(Map<String, Object> map);
- 編寫測試類測試上述存儲過程; 通過map的put傳入輸入?yún)?shù),通過map的get方法獲取輸出參數(shù)的值
// 根據(jù)存儲過程查詢某個年級的學(xué)生總數(shù)
public static void queryCountByGradeWithProcedure() throws IOException {
SqlSession sqlSession = getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("gName", "class1");
mapper.queryCountByGradeWithProcedure(map); // 調(diào)用存儲過程并傳入輸入?yún)?shù)
Object sCount = map.get("sCount");
System.out.println(map.size());
System.out.println(map);
System.out.println(map.get("gName") + " 年級的學(xué)生總數(shù)是: " + sCount);
sqlSession.commit();
sqlSession.close();
}
// 根據(jù)存儲過程刪除某個stuno的學(xué)生
public static void deleteStudentByStuNoWithProcedure() throws IOException {
SqlSession sqlSession = getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("stuno", 9);
mapper.deleteStudentByStuNoWithProcedure(map);
sqlSession.commit();
sqlSession.close();
}
-
注意:
如果報錯: No enum constant org.apache.ibatis.type.JdbcType.xx: 說明MyBatis不支持xx類型, 需要檢查類型
存儲過程無論輸入?yún)?shù)是什么值, 語法上都需要用map 來傳遞該值
主配置文件 conf.xml 中 transactionManager 配置的是 JDBC 的話, 增刪改都需要調(diào)用SqlSession對象的commit()方法手工提交事務(wù)
7. 輸出參數(shù): resultType
-
resultType 為簡單類型(8基本+String)
- 在xxxMapper.xml 的SQL標(biāo)簽中定義 resultType為簡單類型或者字符串; 接口中增加抽象方法, 其返回值為resultType定義的簡單類型, 編寫測試方法測試
-
resultType 為對象/對象集合類型
注意: 不管輸出的是對象還是對象類型的的集合, resultType 中只寫對象, 不寫集合類型的對象
使用方法類似于使用基本類型
-
resultType 為HashMap
指定resultType輸出類型為 HashMap, 并給SQL語句的每個字段加上別名
接口的抽象方法中, 返回值類型為 Map<String, Object>
測試方法中, 根據(jù) Mapper.xml 中SQL語句中定義的別名作為 Map 的 key 來獲取對應(yīng)的 value
注意1: 一個 HashMap 對應(yīng)一個學(xué)生的多個元素(也就是多個屬性); 如果要查詢多個學(xué)生, 就需要多個HashMap
注意2: 一般情況下使用 resultType, 但是實體類屬性和數(shù)據(jù)表的字段存在類型, 名字不一致時, 使用 resultMap
注意3: 當(dāng)屬性名和字段名不一致時, 除了使用resultMap 之外, 還可以受用 resultType + HashMap
注意4: 如果發(fā)現(xiàn)獲取的值中某個字段始終是默認(rèn)值, 則可能是表的字段和屬性的值類型或名字不一致導(dǎo)致的, 需要解決類型不一致或名字不一致問題
使用 resultMap 解決屬性和字段不一致問題以及指定 resultMap 標(biāo)簽
<!-- type:指定返回值的類型 id:唯一標(biāo)識符
區(qū)分主鍵和非主鍵, 主鍵通過id指定, 非主鍵通過result指定
先將數(shù)據(jù)庫字段的字段名改了:
alter table student rename column stuNo to id;
alter table student rename column stuName to name;
-->
<resultMap id="resultMap01" type="Student">
<id property="stuNo" column="id"/>
<result property="stuName" column="name"/>
<result property="stuAge" column="StuAge"/>
</resultMap>
<!-- 使用 resultMap: 需要指定resultMap 標(biāo)簽 -->
<select id="queryStudentByIdWithResultMap" parameterType="int" resultMap="resultMap01">
select id, name, stuAge from student where id = #{id}
</select>
<!--接著:寫接口, 寫接口的測試類 -->
- 使用 resultType + HashMap 解決 屬性和字段不一致問題
<!-- 使用resultType + HashMap 解決屬性和字段不一致問題; 原理就是給SQl的字段加別名-->
<select id="queryStudentByIdWithHashMap" parameterType="int" resultType="HashMap">
select id 'stuNo', name 'StuName', stuAge 'age' from student where stuNo = #{stuNo}
</select>
<!--
1. 接口中的返回值需要設(shè)置成Map 或者 List<Map<String, Map>> 的形式
2. 獲取值的時候, 通過 別名獲取
-->
8. 動態(tài)SQL
if, where 以及 trim 標(biāo)簽
<\where> 標(biāo)簽會自動處理拼接SQL中第一個<\if>標(biāo)簽中的and, 但不會處理之后<\if>中的and
-
<\trim> 可以處理拼接SQL中第一個和最后一個and; 如: <\trim prefix="where" prefixOverrides="and">
prefix="where" 表示給拼接SQL加 where
prefixOverrides="and" 表示處理拼接SQL中第一個and
suffixOverrides="and" 表示處理拼接SQL中最后結(jié)尾多出來的and
if 標(biāo)簽中 test 測試的就是parameterType中指定的屬性
<!-- SQL動態(tài)標(biāo)簽: 根據(jù)名字或年齡來查詢學(xué)生信息 -->
<!-- SQL 語句中采用條件判斷, 根據(jù)測試方法傳入的值來決定后面跟那句話 -->
<select id="queryStuByNaOrAgWithTag" parameterType="Student" resultType="Student">
select stuNo, stuAge, stuName from student
<where>
<if test="stuName != null and stuName != ''">
and stuName = #{stuName}
</if>
<if test="graName != null and graName != ''">
and graName = #{graName}
</if>
<if test="stuAge != null and stuAge != ''">
and stuAge = #{stuAge}
</if>
</where>
</select>
<!--
<trim> 標(biāo)簽可以處理拼接SQL中第一個和最后一個and; 也可以添加前綴
-->
<!-- prefixOverrides: 會自動處理多出來的第一個and -->
<trim prefix="where" prefixOverrides="and">
<if test="stuName != null and stuName != ''">
and stuName = #{stuName}
</if>
<if test="graName != null and graName != ''">
and graName = #{graName}
</if>
<if test="stuAge != null and stuAge != ''">
and stuAge = #{stuAge}
</if>
</trim>
<!-- suffixOverrides: 會自動處理多出來的最后一個and -->
<trim prefix="where" suffixOverrides="and">
<if test="stuName != null and stuName != ''">
stuName = #{stuName} and
</if>
<if test="graName != null and graName != ''">
graName = #{graName} and
</if>
<if test="stuAge != null and stuAge != ''">
stuAge = #{stuAge} and
</if>
</trim>
trim 處理update語句的示例
- .mapper.xml示例
<!-- 修改: 使用trim實現(xiàn)可以隨意修改 -->
<update id="updateStudentByStuNoTrim" parameterType="Student">
update student
<trim prefix="set" suffixOverrides=",">
<if test="stuName != null and stuName != ''">
stuName=#{stuName} ,
</if>
<if test="stuAge != null and stuAge != ''">
stuAge=#{stuAge} ,
</if>
<if test="graName != null and graName != ''">
graName=#{graName} ,
</if>
<if test="stuSex != null and stuSex != ''">
stuSex=#{stuSex} ,
</if>
</trim>
where stuNo=#{stuNo}
</update>
- 接口及測試方法
// 接口: 修改: 使用trim實現(xiàn)可以隨意修改
Boolean updateStudentByStuNoTrim(Student student);
// 測試方法: trim: 實現(xiàn)所以的修改
public static void updateStudentByStuNoTrim() throws IOException {
SqlSession sqlSession = getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setStuNo(2);
// student.setStuName("ls");
// student.setStuAge(10);
student.setGraName("class2");
Boolean aBoolean = mapper.updateStudentByStuNoTrim(student);
if(aBoolean){
System.out.println("修改成功!");
}
sqlSession.commit();
sqlSession.close();
}
foreach 標(biāo)簽
- foreach 可以迭代的類型: 數(shù)組, 對象數(shù)組, 集合, 屬性(Grade類, 類里面的屬性的類型就是一個List)
foreach 迭代屬性
foreach 標(biāo)簽迭代屬性時, 是將屬性的類型作為集合來迭代
-
foreach 標(biāo)簽屬性:
collection: 要迭代的屬性的集合
open: 即要迭代的語句的前部分, 左雙引號內(nèi)的左邊需要加空格(加了空格才能組成SQL語句, 否則兩個單詞連起來的, 就錯了)
close: 即要迭代的語句的后半部分 也是雙引號內(nèi)的右邊需要加空格
item: 就是給每次迭代出來的值起的名, 在方法體中需要#{item}來獲取值
separator: 就是分隔符, 沒有分隔符的話兩次迭代的item的值會拼接在一起, 所以必須指定分隔符
<!-- 將多個元素放在對象的屬性中 -->
<select id="queryStudentsWithNoInGrade" parameterType="Grade" resultType="Student">
select * from student
<where>
<if test="array != null and array.size >0">
<foreach collection="array" open=" and stuNo in (" close=") " item="stuNo" separator=",">
#{stuNo}
</foreach>
</if>
</where>
</select>
foreach 迭代簡單類型的數(shù)組
無論編寫代碼時, 傳遞的是什么參數(shù)名, 在 mapper.xml 中必須使用 array 代替該簡單數(shù)組
當(dāng)使用 foreach 遍歷數(shù)組的時候, 在if條件, foreach 中, 參數(shù)名固定寫法是 array
<!-- 將多個元素放在數(shù)組中 -->
<select id="queryStudentsWithArray" parameterType="int[]" resultType="Student">
select * from student
<where>
<if test="array != null and array.length >0">
<foreach collection="array" open=" and stuNo in (" close=")" item="stuNo" separator=",">
#{stuNo}
</foreach>
</if>
</where>
</select>
foreach 迭代集合
- 無論編寫代碼時, 傳遞的是什么參數(shù)名, 在 mapper.xml 中必須使用 list 代替該集合
<!-- 將多個元素放在集合中 List<Integer> -->
<select id="queryStudentsWithList" parameterType="list" resultType="Student">
select * from student
<where>
<if test="list != null and list.size >0">
<foreach collection="list" open=" and stuNo in (" close=")" item="stuNo" separator=",">
#{stuNo}
</foreach>
</if>
</where>
</select>
foreach 迭代對象的數(shù)組
無論編寫代碼時, 傳遞的是什么參數(shù)名, 在 mapper.xml 中必須使用 object[] 代替該對象的數(shù)組
在使用時使用的是對象, 所以在具體的#{}中,使用的是對象.屬性
<!-- 將多個元素放在對象數(shù)組中 -->
<select id="queryStudentsWithObjectArray" parameterType="object[]" resultType="Student">
select * from student
<where>
<if test="array != null and array.length >0">
<foreach collection="array" open=" and stuNo in (" close=")" item="student" separator=",">
#{student.stuNo}
</foreach>
</if>
</where>
</select>
SQL片段(將相似的代碼提取出來)
第一步: 提取相似代碼, 寫在 <sql> 標(biāo)簽中, 通過 id 來讓需要的地方引用
第二部: 引用: 在SQL標(biāo)簽中通過<include>標(biāo)簽的refid來引用即可; 跨文件引用需要加上前綴 namespace
<!-- 提起代碼片段 -->
<sql id="baseSql" >
select * from student
</sql>
<!-- 引用代碼片段 -->
<select id="testBaseSql" resultType="Student">
<include refid="baseSql" />
</select>
9. 關(guān)聯(lián)查詢
- MyBatis主要學(xué)習(xí)兩個, 一對一和一對多, (MyBatis認(rèn)為多對多的本質(zhì)就是一對多的變化)
一對一
- 一對一通過類型擴展類和resultMap來實現(xiàn)一對一
業(yè)務(wù)擴展類實現(xiàn)一對一(不適用于大型項目)
業(yè)務(wù)擴展類實現(xiàn)一對一的核心就是通過一個擴展類實現(xiàn)兩個類的屬性(其實也就是兩個表中的全部字段)
這樣做的原因是resultType所代表的返回值類型不能寫兩個實體類
先設(shè)計數(shù)據(jù)庫表以及實現(xiàn)表的映射類
-- 創(chuàng)建 cardinfo 表
create table studentCard (
cardid int(10) primary key,
cardinfo varchar(20)
);
-- 增加數(shù)據(jù)
insert into studentCard values(1, 'zs info ...');
insert into studentCard values(2, 'ls info ...');
-- student 表增加cardid 欄
alter table student add column cardid int(10);
-- 增加外鍵關(guān)聯(lián)
alter table student add constraint fk_student_stundetCard_cardid foreign key (cardid) references studentCard (cardid);
-- Student 表增加cardid 數(shù)據(jù)
update student set cardid = 1 where stuNo = 1;
update student set cardid = 2 where stuNo = 2;
-
studentMapper.xml
- 因為是要返回多個類的數(shù)據(jù), 但是 resultType 只能寫一個類, 所以就想辦法, 將兩個類的屬性整合到一個類當(dāng)中; (實現(xiàn)辦法就是繼承一個屬性多的類, 而重寫一個屬性少的類)
<!-- 利用業(yè)務(wù)擴展類實現(xiàn)一對一 -->
<select id="queryStudentByOneToOne" resultType="StudentBusiness" parameterType="int">
select s.*, c.* from student s inner join studentcard c
on s.cardid = c.cardid
where s.stuNo = #{stuNo}
</select>
- 創(chuàng)建 StudentBusiness 類繼承Student類, 自然就繼承了Student的所有屬性, 并在此類中重寫 StudentCard 類的屬性; 這樣StudentBusiness 類就有了兩個類的屬性, 也就相當(dāng)于有了兩個數(shù)據(jù)表的字段
public class StudentBusiness extends Student {
private int cardId;
private String cardInfo;
public int getCardId() {
return cardId;
}
public void setCardId(int cardId) {
this.cardId = cardId;
}
public String getCardInfo() {
return cardInfo;
}
public void setCardInfo(String cardInfo) {
this.cardInfo = cardInfo;
}
@Override
public String toString() {
return "StudentBusiness{" +
"cardId=" + cardId +
", cardInfo='" + cardInfo + '\'' +
"} " + super.toString();
}
}
- 寫測試方法測試該接口
resultMap 實現(xiàn)一對一(重點在于 resultMap 標(biāo)簽)
業(yè)務(wù)擴展類是通過一個擴展類包含兩個表的字段來完成一對一
resultMap 是通過兩個類互相持有對方為屬性成員來將兩個類關(guān)聯(lián), 就相當(dāng)于數(shù)據(jù)表中通過外鍵將兩個表關(guān)聯(lián)
使用 resultMap 來指定返回值類型是, 需要單獨指定resultMap標(biāo)簽(type代表返回值實體類, 該實體類需要包含兩張表的所有字段), 并且主鍵使用 id, 非主鍵使用 result, 對象成員使用 association, 其中 association 的 javaType 指定該屬性的類型, property 指定屬性名
studentMapper.xml
<resultMap id="student_card_map" type="Student">
<!-- 學(xué)生的信息 -->
<id property="stuNo" column="stuNo"/>
<result property="stuName" column="stuName" />
<result property="stuAge" column="stuAge"/>
<result property="stuSex" column="stuSex"/>
<result property="graName" column="graName"/>
<!-- 一對一時, 對象成員使用 association映射, javaType指定該屬性的類型 -->
<association property="studentCard" javaType="StudentCard">
<id property="cardId" column="cardId"/>
<result property="cardInfo" column="cardInfo"/>
</association>
</resultMap>
<!-- resultMap 來完成一對一, 兩個類互相持有對方的成員變量 -->
<select id="queryStudentByRMap" parameterType="int" resultMap="student_card_map">
select s.stuNo, s.stuName, s.stuAge, s.stuSex, s.graName, c.cardid, c.cardinfo from student s
inner join studentcard c on s.cardid = c.cardid
where s.stuNo = #{stuNo}
</select>
- 接口的方法中, 返回值是 Student, 測試時, 通過 this.studentCard.getCardId 就能獲取關(guān)聯(lián)表的屬性
一對多
- 先設(shè)計數(shù)據(jù)庫表; 一個班級可以對應(yīng)多個學(xué)生, 品一下昨天想返回多個resultMap; 兩張表是通過 classId 的外鍵來關(guān)聯(lián)的
-- 創(chuàng)建 StudentClass 表
create table studentClass (
classId int(10) primary key,
className varchar(20)
);
-- 增加數(shù)據(jù)
insert into studentClass values(1, 'g1');
insert into studentClass values(2, 'g2');
-- student 表增加classId 欄
alter table student add column classId int(10);
-- 增加外鍵關(guān)聯(lián)
alter table student add constraint fk_student_stundetClass_classId foreign key (classId) references stundetClass (classId);
-- Student 表增加classId數(shù)據(jù)
update student set classId = 1 where stuNo = 1;
update student set classId = 2 where stuNo = 2;
創(chuàng)建 StudentClass.java 的實體類, 通過List<Student> students來完成一個班級對應(yīng)多個學(xué)生的映射關(guān)系
-
studentMapper.xml 中, 通過resultMap 指定返回的類型
注意1: tpye 描述的是返回的類型, 所以type類型寫的是誰, resultMap就把誰當(dāng)作主類來配置
注意2: 一對多的成員屬性的配置標(biāo)簽是 collection, 區(qū)別于一對一使用 association 來配置(記住)
注意3: 成員屬性的類型是 List<Student>, 但是 javaType 不可以這樣配置, 所以就使用 ofType來配置該屬性的元素的類型
注意4: 元素的類型使用javaType, 元素的屬性的類型使用 ofType
<!-- 下面是一對多的表關(guān)聯(lián), 一對多通過resultMap 來完成關(guān)聯(lián)
類 - 表的對應(yīng)關(guān)系
-->
<resultMap id="student_class_map" type="StudentClass">
<!-- 因為type返回的主類是 studentClass, 所以先配置 StudentClass -->
<id property="classId" column="classId"/>
<result property="className" column="className"/>
<!-- 配置成員屬性, 一對一是association, 一對多是 collection -->
<!-- ofType: 描述該屬性的元素類型; javaType: 描述該屬性的類型
這里的屬性類型是 List<Student>, 但是 javaType里面不能這樣寫, 所以這里使用 ofType來寫該屬性的元素的類型
-->
<collection property="students" ofType="Student">
<id property="stuNo" column="stuNo"/>
<result property="stuName" column="stuName"/>
<result property="stuAge" column="stuAge"/>
<!-- 這里還可以接著配 Student 內(nèi)的 studentCard的信息, 使用的標(biāo)簽是 association -->
</collection>
</resultMap>
<!-- 查詢g1班的信息, 和g1班的所有學(xué)生的信息 -->
<select id="queryClassAndStudents" resultMap="student_class_map" parameterType="int">
select c.*, s.* from student s inner join studentclass c
on s.classId = c.classId
where c.classId = #{classId}
</select>
- 接口抽象方法及測試類
// 表關(guān)聯(lián), 一對多, 通過resultMap de collection 子標(biāo)簽
StudentClass queryClassAndStudents(int classId);
// 查詢班級和班級對應(yīng)的學(xué)生
public static StudentClass queryClassAndStudents(int classId) throws IOException {
SqlSession sqlSession = getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
StudentClass studentClass = mapper.queryClassAndStudents(classId);
System.out.println(studentClass);
List<Student> students = studentClass.getStudents();
for (Student student : students) {
System.out.println(student.getStuNo() + " - "+ student.getStuName() +" - "+ student.getStuAge());
}
sqlSession.close();
return studentClass;
}
10. 使用 log4j 日志
導(dǎo)入 log4j-1.2.17.jar (下載的MyBatis壓縮包的lib目錄中已有)
在 conf.xml 通過配置開啟日志; 如果不開啟就會按照 (SJF4J -> Apache CommonsLogging -> Log4j 2 -> log4j -> JDK logging) 的順序來尋找日志
<!-- 設(shè)置全局參數(shù)-->
<settings>
<!-- 開啟日志, 并指定使用的具體日志 -->
<setting name="logImpl" value="LOG4J"/>
</settings>
-
編寫日志輸出文件 log4j.properties
log4j日志級別(從高到低): ERROR, WARN, INFO, DEBUG
開發(fā)階段使用 DEBUG, 在生產(chǎn)階段使用 WARN 或者更高的級別
### 設(shè)置Logger輸出級別和輸出目的地 ###
log4j.rootLogger=DEBUG, Console
### 生產(chǎn)環(huán)境使用
# log4j.rootLogger=WARN, FILE
#### Console 日志輸出到控制臺
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
## 帶時間的
# log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.appender.Console.layout.ConversionPattern=[%t] %-5p - %m%n
log4j.appender.Console.encoding=UTF-8
#### File 日志輸出到文件; 需要再 rootLogger 后面增加: , File
#log4j.appender.File = org.apache.log4j.FileAppender
#log4j.appender.File.File = D://File//Java//JAVA_WEB//Log4J_Log
#log4j.appender.File.layout = org.apache.log4j.PatternLayout
#log4j.appender.File.layout.ConversionPattern =%d [%t] %-5p [%c] - %m%n
- 使用: 使用log4j日志的好處是可以觀察到MyBatis實際執(zhí)行SQL語句的過程以及SQL執(zhí)行時的參數(shù)及返回的結(jié)果等信息
11. 延遲加載(懶加載)
如果不采用延遲加載(就是立即加載), 查詢時會將 一 和 多 都查詢, 如班級, 班級中的所有學(xué)生; 如果想要暫時只查詢 一, 而多的一方先不查詢, 而是在需要的時候再去查詢, 這個過程就是延遲加載
理解: 延遲加載就是關(guān)聯(lián)查詢時, 通過配置實現(xiàn)按需查詢, 并不是將所有關(guān)聯(lián)的數(shù)據(jù)一次性都查出來, 而是在需要的時候再查詢(可以通過DEBUG來查看)
一對一延遲加載例子
- 延遲加載, 需要將延遲查詢的表單獨放在一個Mapper.xml 中, 在 resultMap 的 association 標(biāo)簽內(nèi)通過 select屬性指定 namespace.id; 以及在column兩個表之間關(guān)聯(lián)的外鍵
<!-- studentCardMapper.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">
<mapper namespace="xyz.xmcs.Mapper.StudentCardMapper">
<!-- 查詢學(xué)生證信息 -->
<select id="queryCardById" parameterType="int" resultType="StudentCard">
<!-- 查詢學(xué)生對應(yīng)的學(xué)生證
根據(jù)cardId 查詢學(xué)生證的SQL在: xyz.xmcs.Mapper.StudentCardMapper.queryCardById
-->
select * from studentcard where cardid = #{cardId}
</select>
</mapper>
<!-- StudentMapper.xml 中延遲加載一對一的resultMap 及 select 標(biāo)簽 -->
<resultMap id="student_card_lazyLoad_map" type="Student">
<!-- 學(xué)生的信息 -->
<id property="stuNo" column="stuNo"/>
<result property="stuName" column="stuName" />
<result property="stuAge" column="stuAge"/>
<result property="stuSex" column="stuSex"/>
<result property="graName" column="graName"/>
<!-- 一對一時, 對象成員使用 association映射, javaType指定該屬性的類型
采用延遲加載, 在查詢學(xué)生是, 并不立即加載學(xué)生證
-->
<!-- 學(xué)生證 在需要的時候通過select查詢學(xué)生證信息-->
<association property="studentCard" javaType="StudentCard" select="xyz.xmcs.Mapper.StudentCardMapper.queryCardById" column="cardId">
<!-- <id property="cardId" column="cardId"/>
<result property="cardInfo" column="cardInfo"/>-->
</association>
</resultMap>
<!-- 下面的例子是延遲加載, 一對一的延遲加載 -->
<select id="queryStudentOtOWithLazyLoad" parameterType="int" resultMap="student_card_lazyLoad_map">
select * from student
</select>
- 在 conf.xml 總配置文件中配置延遲加載
<!-- 設(shè)置全局參數(shù)-->
<settings>
<!-- 開啟延遲加載 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 關(guān)閉立即加載 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 上面的配置沒錯, 但是運行就報 Error creating lazy proxy. Cause: java.lang.NullPointerException
的異常, 看了網(wǎng)上的解決方案是將 MyBatis的jar包更新到3.5.1
-->
</settings>
<!-- 將 StudentMapper.xml 添加進來 -->
<mapper resource="xyz/xmcs/Mapper/studentCardMapper.xml"/>
- 編寫接口方法及實現(xiàn)測試方法
// 延遲加載, 一對一
List<Student> queryStudentOtOWithLazyLoad();
// 延遲加載, 一對一, 查詢?nèi)繉W(xué)生, 并延遲加載學(xué)生的學(xué)生證
public static List<Student> queryStudentOtOWithLazyLoad() throws IOException {
SqlSession sqlSession = getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.queryStudentOtOWithLazyLoad();
for (Student student : students) {
System.out.println(student.getStuNo() + " - "+student.getStuName()+ " - "+student.getStuAge());
// 根據(jù)學(xué)生查詢學(xué)生證
StudentCard card = student.getStudentCard();
System.out.println(card.getCardId() + " - "+ card.getCardInfo());
}
sqlSession.close();
return students;
}
一對多延遲加載
一對多和一對一的原理是一樣的, 首先確保再總配置文件中開啟了延遲加載, 然后在配置一的一方的select以及resultMap, 并且在resultMap中配置 collection子標(biāo)簽
配置多的一方的 select 標(biāo)簽; 在一的一方的 connection 標(biāo)簽的 select 屬性中通過 namespace.id 引入多的一方的 select 標(biāo)簽; 在 collection 標(biāo)簽中, 通過 property指定多的一方的屬性名, 通過 column指定關(guān)聯(lián)的外鍵名, 通過ofType指定多的一方屬性的元素的類型 (應(yīng)為JavaType 不能指定集合類型)
studentClassMapper.xml 以及 studentMapper.xml 中的 select 標(biāo)簽
<?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 指的是 StudentClassMapper 接口的全類名 -->
<mapper namespace="xyz.xmcs.Mapper.StudentClassMapper">
<resultMap id="student_class_lazyLoad_map" type="StudentClass">
<!-- 因為type返回的主類是 studentClass, 所以先配置 StudentClass -->
<id property="classId" column="classId"/>
<result property="className" column="className"/>
<collection property="students" ofType="Student" select="xyz.xmcs.Mapper.StudentMapper.queryAllStudentLazyByStuNo" column="classId">
<!-- 再記一遍, 一對一: association; 一對多: collection
因為是對多, 所以類中的屬性是 List<Xxx> 類型的, 所以collection中不能用javaType; 而只能使用表示元素的類型的 ofType
select中指定需要延遲加載的SQL語句的 namespace.id; column指定外鍵欄; property 指定的是多的屬性名(類文件中的)
-->
</collection>
</resultMap>
<!-- 延遲加載: 一對多 -->
<select id="queryClassAndStudentsLazy" resultMap="student_class_lazyLoad_map">
<!-- 先查班級, 以及全部班級的學(xué)生 -->
select * from studentclass
</select>
</mapper>
<!-- studentMapper.xml select 標(biāo)簽 -->
<!-- 一對多: 延遲加載需要的: 查詢班級中的所有學(xué)生 -->
<select id="queryAllStudentLazyByStuNo" resultType="Student" parameterType="int">
select * from student where classId = #{classId}
</select>
- 接口以及測試類
// 延遲加載: 一對多; StudentClassMapper.java
List<StudentClass> queryClassAndStudentsLazy();
// 測試一對多延遲加載; TestStudent.java
public static List<StudentClass> queryClassAndStudentsLazy() throws IOException {
SqlSession sqlSession = getSqlSession();
StudentClassMapper mapper = sqlSession.getMapper(StudentClassMapper.class);
List<StudentClass> studentClasses = mapper.queryClassAndStudentsLazy();
// 班級信息
for (StudentClass aClass : studentClasses) {
System.out.println(aClass);
// 學(xué)生信息
for(Student student : aClass.getStudents()){
System.out.println(student.getStuNo() + " -- " + student.getStuName() + " -- " + student.getStuAge());
}
}
sqlSession.close();
return studentClasses;
}
12. 查詢緩存
- Mybatis 框架提供了緩存策略谜酒,通過緩存策略可以減少查詢數(shù)據(jù)庫的次數(shù)出牧,提升系統(tǒng)性能; 在 Mybatis 框架中 緩存分為一級緩存和二級緩存
一級緩存
一級緩存: 第一次查詢后, 將查詢的結(jié)果保存在SqlSession對象里放在內(nèi)存中, 在接下來查詢時, 會先在內(nèi)存中的SqlSession對象查找是不是有緩存, 有則使用緩存, 沒有則再查詢數(shù)據(jù)庫
一級緩存是 sqlSession 范圍的緩存淤年,只能在同一個 sqlSession 內(nèi)部有效; 使用commit會清空一級緩存
MyBatis 默認(rèn)是開啟一級緩存的, 如果用同一個SqlSession對象查詢相同的數(shù)據(jù), 則只會在第一次查詢時向數(shù)據(jù)庫發(fā)送SQL語句, 并將結(jié)果放在SqlSession中(作為緩存存在); 后續(xù)再次查詢該同樣的對象時, 則直接從緩存中查詢該對象即可(即省略了數(shù)據(jù)庫的訪問, 從而提高性能)
二級緩存
MyBatis 自帶二級緩存
-
二級緩存: 同一個namespace生成的mapper對象; MyBatis 默認(rèn)沒有開啟二級緩存, 需要再 settings 中手動配置打開
手動打開二級緩存: <\setting name="cacheEnabled" value="true"/> 再具體的Mapper.xml 中聲明開啟二級緩存 <\cache/>
回顧: namespace 的值就是接口的全類名(包名.類名); 通過接口可以產(chǎn)生代理對象(即接口的對象), 所以即namespace決定了接口產(chǎn)生對象的產(chǎn)生
二級緩存是將對象緩存在硬盤當(dāng)中(序列化:就是從內(nèi)存到硬盤; 反序列化:就是從硬盤到內(nèi)存); 所以準(zhǔn)備緩存的對象必須實現(xiàn)序列化(并且該對象的父類, 級聯(lián)屬性都需要實現(xiàn)序列化)
觸發(fā)將對象寫入二級緩存的時機: SqlSession 對象的 closee() 方法
只要產(chǎn)生的XxxMapper對象來自于同一個namespace, 則這些對象共享二級緩存(即用來區(qū)分兩個對象是否共享二級緩存: 只看是否來自于同一個namespace, 如果多個Mapper.xml的namespace值相同, 則它們之間也是共享二級緩存)
如果同一個SqlSession對象進行多次相同的查詢, 則直接進入一級緩存; 如果不是同一個SqlSession對象進行多次相同的查詢(但均來自同一個nameSpace),則進入二級緩存
禁用, 清理二級緩存
禁用某一條select的二級緩存, 只需要在相關(guān)的SQL語句中添加 usecache="false"
禁用二級緩存示例
<!-- useCache: false表示該SQL語句禁用二級緩存, 不寫默認(rèn)就是true -->
<select id="queryStudentByStuNo" resultType="Student" parameterType="int" useCache="false">
select * from student where stuNo = #{stuNo}
</select>
-
清理緩存:
-
第一種方式: 一級,二級緩存, 都是在 commit 的時候清理; 執(zhí)行增刪改的時候執(zhí)行commit后, 會清理掉緩存(目的(這樣設(shè)計的原因): 為了防止讀取臟數(shù)據(jù))
- 注意: 這個 commit 不能說查詢自身的 commit, 而是 增刪改的 commit
第二種方式: 在相關(guān)的select標(biāo)簽中添加 flushCache="true"
-
-命中率: 1:0.0 2:0.5 3:0.666 4:0.75
第三方二級緩存(ehcache)
想要整個第三方(或者自定義)的二級緩存, 必須實現(xiàn)org.apache.ibatis.cache.Cache接口, 該接口的默認(rèn)實現(xiàn)類是PerpetalCache
整合 EhCahce 需要三個jar包: Ehcache-core.jar; mybatis-Ehcache.jar; slf4j-api.jar
編寫配置文件: Ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!-- 當(dāng)二級緩存的對象超過內(nèi)存限制時 (緩存對象的個數(shù)>maxElementsInMemory), 存放入硬盤文件 -->
<diskStore path="D:\Ehcache"/>
<!--
maxElementsInMemory: 設(shè)置內(nèi)存中緩存對象的個數(shù)
maxElementsOnDisk: 設(shè)置在硬盤中緩存對象的個數(shù)
eternal: 設(shè)置緩存是否永遠不過期
overflowToDisk: 當(dāng)內(nèi)存中緩存的對象個數(shù)超過 maxElementsInMemory 的時候, 是否轉(zhuǎn)移到硬盤中
timeToIdleSeconds: 當(dāng)兩次訪問超過該值的時候, 將緩存對象失效
timeTlLiveSeconds: 一個緩存對象做多存放的時間(生命周期)
diskExpiryThreadIntervalSeconds設(shè)置每隔多長時間, 通過一個線程來清理硬盤中的緩存
memoryStoreEvictionPolicy: 當(dāng)內(nèi)存中緩存對象達到最大數(shù), 如果有新的對象加入緩存時, 移除已有緩存對象的策略;默認(rèn)是LRU(最近最少使用); LFU(最不常使用); FIFO(先進先出)
-->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="1000000"
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="100"
timeTlLiveSeconds="100"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
- 開啟EhCahce二級緩存
<!-- StudentMapper.xml -->
<cache type="org.mybatis.caches.encache.EhcacheCache">
<!-- 還可以設(shè)置自定義值, 覆蓋 Ehcache.xml 中的值 -->
<property name="maxElementsInMemory" value="2000" />
<property name="maxElementsOnDisk" value="3000" />
</cache>
13. MyBatis逆向工程
- 表, 類, 接口, mapper.xml 四者密切相關(guān), 因此當(dāng)知道一個的時候, 其他三個理論上應(yīng)該可以自動給生成
由表 -> 生成其他的(實現(xiàn)步驟)
需要下載: mybatis-generator-core.jar
逆向工程配置文件: generator.xml
<!--from: 藍橋?qū)W院/mybatiis/逆向工程 -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<!--
suppressAllComments屬性值:
true:自動生成實體類腿时、SQL映射文件時沒有注釋
true:自動生成實體類顶伞、SQL映射文件寸莫,并附有注釋
-->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!-- 數(shù)據(jù)庫連接信息 : Oracle
<jdbcConnection driverClass="oracle.jdbc.OracleDriver"
connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:XE"
userId="system" password="sa">
</jdbcConnection>-->
<!-- 數(shù)據(jù)庫鏈接信息: Mysql -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="url=jdbc:mysql://127.0.0.1:3306/MyBatisTest"
userId="root" password="root">
</jdbcConnection>
<!--
forceBigDecimals屬性值:
true:把數(shù)據(jù)表中的DECIMAL和NUMERIC類型开仰,解析為JAVA代碼中的java.math.BigDecimal類型
false(默認(rèn)):把數(shù)據(jù)表中的DECIMAL和NUMERIC類型脐往,解析為解析為JAVA代碼中的Integer類型
-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--
targetProject屬性值:實體類的生成位置
targetPackage屬性值:實體類所在包的路徑
-->
<javaModelGenerator targetPackage="xyz.xmcs.entity"
targetProject=".\src">
<!-- trimStrings屬性值:
true:對數(shù)據(jù)庫的查詢結(jié)果進行trim操作
false(默認(rèn)):不進行trim操作
-->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--
targetProject屬性值:SQL映射文件的生成位置
targetPackage屬性值:SQL映射文件所在包的路徑
-->
<sqlMapGenerator targetPackage="xyz.xmcs.mapper"
targetProject=".\src">
</sqlMapGenerator>
<!-- 生成動態(tài)代理的接口 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="xyz.xmcs.mapper" targetProject=".\src">
</javaClientGenerator>
<!-- 指定數(shù)據(jù)庫表 -->
<table tableName="Student"> </table>
<table tableName="studentCard"> </table>
<table tableName="studentClass"> </table>
</context>
</generatorConfiguration>
- 編寫測試類
public class Test {
public static void main(Stirng[] args) {
File file = new File("src/generator.xml"); // 配置文件
List<String> warnings = new ArrayList<>(); // 警告的集合
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(file);
DefaultShellCallback callBack = new DefaultShellCallback(true);
// 逆向工程核心類
MyBatisGenerator generator = new MyBatisGenerator(config, callBack, warnings);
generator.generate(null);
}
}
// 逆向工程說實在的不實用