MyBatis是一個(gè)半自動(dòng)的ORM框架,它要求開發(fā)者編寫具體的SQL語句平挑。
MyBatis解決的問題:
JDBC使用復(fù)雜艘策,需要操作Connection、Statement撕贞、ResultSet等對象更耻,并要處理異常、正確關(guān)閉資源等捏膨。
MyBatis是一種ORM模型秧均。ORM簡單來說就是數(shù)據(jù)庫表和JAVA對象相互映射
一.配置MyBatis
每個(gè)MyBatis的應(yīng)用程序都以一個(gè)SqlSessionFactory對象實(shí)例為核心食侮。這個(gè)對象由SqlSessionFactoryBuilder從XML配置文件或Configuration(?)類的實(shí)例中構(gòu)建SqlSessionFactory對象目胡。
1.1在Spring中配置MyBatis
將mybatis-spring依賴在pom.xml中引入
在Mybatis.xml配置dataSource锯七、sqlSessionFactory、MapperScannerConfigurer
注:配置文件的名字可以自己起誉己,不一定是MyBatis.xml
dataSource:數(shù)據(jù)源眉尸,這個(gè)只要連接數(shù)據(jù)庫都要配置
sqlSessionFactory:注入數(shù)據(jù)源,配置文件巨双,sql映射文件掃描位置噪猾。注意mapperLocations屬性,用它說明sql映射文件的存儲位置筑累,不用再依次列出每個(gè)文件了袱蜡,新添加映射文件時(shí)也不用再做修改。
MapperScannerConfigurer:這是mybatis-spring提供的一個(gè)轉(zhuǎn)換器慢宗,可以將映射接口轉(zhuǎn)換為Spring容器中的Bean坪蚁,這就是為什么我們只定義了dao層接口并沒有實(shí)現(xiàn),卻可以在service層直接注入dao的原因镜沽。MapperScannerConfigure將掃描basePackage包下的所有接口迅细,如果它們在sql映射文件中定義過,則將它們動(dòng)態(tài)定義為一個(gè)Spring Bean淘邻。
下面是一個(gè)配置的具體例子:
<!-- 添加連接池則改變數(shù)據(jù)源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</bean>
<!-- spring和MyBatis整合,不需要在mybatis的配置文件中寫每個(gè)entity的映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!-- 自動(dòng)掃描mapper.xml文件 -->
<property name="mapperLocations" value="classpath:sqlmapper/*.xml"></property>
</bean>
<!-- mapper接口所在包名湘换,Spring會自動(dòng)查找其下的類 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.edu.xidian.see.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
對于將映射接口宾舅,轉(zhuǎn)換為可以使用的實(shí)例,Spring還提供了SqlSessionTemplate彩倚,可以通過其getMapper(Class<T> type)來獲得一個(gè)實(shí)例筹我,通過這個(gè)實(shí)例即可調(diào)用sql映射文件定義的映射項(xiàng)。
二.使用MyBatis
使用MyBatis訪問數(shù)據(jù)庫可以分為三步:
- 定義Mapper映射接口(java接口)帆离。Sql映射文件通過namespace和Mapper接口一一對應(yīng)蔬蕊,每個(gè)select\update\insert等標(biāo)簽對應(yīng)一個(gè)接口中的方法。哥谷。
- 定義MyBatis Sql映射文件岸夯。ResultMap,動(dòng)態(tài)SQL
- 在Service層等調(diào)用處们妥,注入Java接口猜扮。因?yàn)镸apperScannerConfigurer已經(jīng)將接口映射為Spring Bean實(shí)例,可以直接使用Autowire注入Dao监婶。
sql映射文件中的動(dòng)態(tài)SQL一般用于拼接SQL語句旅赢,主要有一下幾種
- <if>
<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">
select * from user where
<if test="username != null">
username=#{username}
</if>
<if test="sex!= null">
and sex=#{sex}
</if>
</select>
- <if> + <where>
上述<if>的例子中齿桃,如果username==null,sex!=null煮盼,就會拼接出錯(cuò)誤的sql語句短纵。將條件放入where標(biāo)簽中,它將去掉多余的and僵控,如果返回的內(nèi)容為空的話香到,它也不會插入‘where’
<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">
select * from user
<where>
<if test="username != null">
username=#{username}
</if>
<if test="sex!= null">
and sex=#{sex}
</if>
</where>
</select>
3.適用于update語句的<set> <if>組合
如果需求是:根據(jù)屬性是否為null來決定是否更新字段那么,可以用此組合喉祭。
如果有多余的“养渴,”set標(biāo)簽會刪除掉
<update id="updateUserById" parameterType="com.ys.po.User">
update user u
<set>
<if test="username != null and username != ''">
u.username = #{username},
</if>
<if test="sex != null and sex != ''">
u.sex = #{sex}
</if>
</set>
where id=#{id}
</update>
4.choose(when,otherwise)語句
依次判斷when的條件,當(dāng)有一個(gè)滿足時(shí)泛烙,結(jié)束判斷理卑,當(dāng)都不滿足是使用otherwise中的語句。
<select id="selectUserByChoose" resultType="com.ys.po.User" parameterType="com.ys.po.User">
select * from user
<where>
<choose>
<when test="id !='' and id != null">
id=#{id}
</when>
<when test="username !='' and username != null">
and username=#{username}
</when>
<otherwise>
and sex=#{sex}
</otherwise>
</choose>
</where>
</select>
5.trim語句
可以實(shí)現(xiàn)where或set的功能蔽氨,是更一般化的標(biāo)簽藐唠,可以指定開頭(prefix)、結(jié)尾(suffix)鹉究、開頭處的多余字符(prefixOverrides)宇立、結(jié)尾處的多余字符(suffixOverrides)
<select id="selectUserByUsernameAndSex" resultType="user" parameterType="com.ys.po.User">
select * from user
<trim prefix="where" prefixOverrides="and | or">
<if test="username != null">
and username=#{username}
</if>
<if test="sex != null">
and sex=#{sex}
</if>
</trim>
</select>
<update id="updateUserById" parameterType="com.ys.po.User">
update user u
<trim prefix="set" suffixOverrides=",">
<if test="username != null and username != ''">
u.username = #{username},
</if>
<if test="sex != null and sex != ''">
u.sex = #{sex},
</if>
</trim>
where id=#{id}
</update>
6.foreach語句
實(shí)現(xiàn)遍歷list。
<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.ys.po.User">
select * from user
<where>
<!--
collection:指定輸入對象中的集合屬性
item:每次遍歷生成的對象
open:開始遍歷時(shí)的拼接字符串
close:結(jié)束時(shí)拼接的字符串
separator:遍歷對象之間需要拼接的字符串
select * from user where 1=1 and id in (1,2,3)
-->
<foreach collection="ids" item="id" open="and id in (" close=") " separator=",">
#{id}
</foreach>
</where>
</select>
三.MyBatis運(yùn)行原理
MyBatis的核心組件:
SqlSessionFactoryBuilder:生成SqlSessionFactory
SqlSessionFactory:生成SqlSession
SqlSession:發(fā)送SQL去執(zhí)行自赔,并返回結(jié)果
MyBatis的運(yùn)行包括兩部分:一妈嘹。讀取配置文件,構(gòu)建SqlSessionFactory對象绍妨。二润脸。SqlSession的執(zhí)行過程。以下分析較為復(fù)雜的第二部分他去。
先看一個(gè)問題:使用MyBatis時(shí)需要定義Mapper接口和SQL映射文件毙驯,這里的Mapper只是個(gè)接口,他是如何執(zhí)行的灾测?
答案就是動(dòng)態(tài)代理爆价。
動(dòng)態(tài)代理有兩種實(shí)現(xiàn):1是JDK通過反射提供的動(dòng)態(tài)代理;2是CGLIB動(dòng)態(tài)代理媳搪。區(qū)別:JDK代理需要提供接口铭段,CGLIB不需要;MyBatis中這兩種方式都使用了秦爆。
一般來說實(shí)現(xiàn)代理稠项,代理的是一個(gè)目標(biāo)類。但是MyBatis使用時(shí)并沒有類鲜结,只有一個(gè)接口展运。這也是可以的活逆。JDK動(dòng)態(tài)代理可以直接生成 一個(gè)接口的實(shí)現(xiàn)類。
JDK動(dòng)態(tài)代理的使用:
//首先是實(shí)現(xiàn)InvocationHandler接口拗胜,實(shí)現(xiàn)invoke方法
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
//method是要被代理執(zhí)行的方法
//args是要傳入的參數(shù)
//被代理類方法的執(zhí)行:
Object result = method.invoke(args);
//可加入其他邏輯
}
//二是要生成代理類蔗候,使用Proxy.newProxyInstance()
//這里的傳入的第三個(gè)參數(shù)this應(yīng)該是一個(gè)實(shí)現(xiàn)InvocationHandler接口的類的對象
//這個(gè)例子是還是先有了一個(gè)目標(biāo)類,但只有接口的情況也是可以的
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
//只有接口的情況
CGLIB
public class HelloServiceCGLib implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("代理之前");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("代理之后");
return result;
}
private Object target;
public Object getProxy(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
}
//Main.java
public class Main {
public static void main(String[] args) {
HelloServiceCGLib helloServiceCGLib = new HelloServiceCGLib();
HelloService target = new HelloServiceImpl();
HelloService helloService = (HelloService) helloServiceCGLib.getProxy(target);
helloService.sayHello("xiaoming");
}
}
動(dòng)態(tài)代理實(shí)現(xiàn)原理
JDK動(dòng)態(tài)代理:
利用反射實(shí)現(xiàn)埂软;
只依賴JDK本身锈遥,不需要依賴外部庫;JDK比外部庫更加可靠勘畔;
需要被代理類實(shí)現(xiàn)接口所灸;
代碼實(shí)現(xiàn)簡單一些。
CGLIB動(dòng)態(tài)代理:
基于ASM實(shí)現(xiàn)炫七;
不需要目標(biāo)類實(shí)現(xiàn)接口爬立;
不能代理final類,因?yàn)椴荒苌善渥宇悾?br>
高性能万哪。
反射:
涉及Class,Field,Method,Constructor等類
SqlSessionFactory的構(gòu)建過程侠驯,略
主要是讀取配置文件到Configuration類,在通過SqlSessionFactoryBuilder類生成