MyBatis是一个半自动的ORM框架,它要求开发者编写具体的SQL语句。
MyBatis解决的问题:
JDBC使用复杂,需要操作Connection、Statement、ResultSet等对象,并要处理异常、正确关闭资源等。
MyBatis是一种ORM模型。ORM简单来说就是数据库表和JAVA对象相互映射
一.配置MyBatis
每个MyBatis的应用程序都以一个SqlSessionFactory对象实例为核心。这个对象由SqlSessionFactoryBuilder从XML配置文件或Configuration(?)类的实例中构建SqlSessionFactory对象。
1.1在Spring中配置MyBatis
将mybatis-spring依赖在pom.xml中引入
在Mybatis.xml配置dataSource、sqlSessionFactory、MapperScannerConfigurer
注:配置文件的名字可以自己起,不一定是MyBatis.xml
dataSource:数据源,这个只要连接数据库都要配置
sqlSessionFactory:注入数据源,配置文件,sql映射文件扫描位置。注意mapperLocations属性,用它说明sql映射文件的存储位置,不用再依次列出每个文件了,新添加映射文件时也不用再做修改。
MapperScannerConfigurer:这是mybatis-spring提供的一个转换器,可以将映射接口转换为Spring容器中的Bean,这就是为什么我们只定义了dao层接口并没有实现,却可以在service层直接注入dao的原因。MapperScannerConfigure将扫描basePackage包下的所有接口,如果它们在sql映射文件中定义过,则将它们动态定义为一个Spring Bean。
下面是一个配置的具体例子:
<!-- 添加连接池则改变数据源 -->
<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的配置文件中写每个entity的映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!-- 自动扫描mapper.xml文件 -->
<property name="mapperLocations" value="classpath:sqlmapper/*.xml"></property>
</bean>
<!-- mapper接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.edu.xidian.see.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
对于将映射接口,转换为可以使用的实例,Spring还提供了SqlSessionTemplate,可以通过其getMapper(Class<T> type)来获得一个实例,通过这个实例即可调用sql映射文件定义的映射项。
二.使用MyBatis
使用MyBatis访问数据库可以分为三步:
- 定义Mapper映射接口(java接口)。Sql映射文件通过namespace和Mapper接口一一对应,每个select\update\insert等标签对应一个接口中的方法。。
- 定义MyBatis Sql映射文件。ResultMap,动态SQL
- 在Service层等调用处,注入Java接口。因为MapperScannerConfigurer已经将接口映射为Spring Bean实例,可以直接使用Autowire注入Dao。
sql映射文件中的动态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,就会拼接出错误的sql语句。将条件放入where标签中,它将去掉多余的and,如果返回的内容为空的话,它也不会插入‘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>组合
如果需求是:根据属性是否为null来决定是否更新字段那么,可以用此组合。
如果有多余的“,”set标签会删除掉
<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的条件,当有一个满足时,结束判断,当都不满足是使用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语句
可以实现where或set的功能,是更一般化的标签,可以指定开头(prefix)、结尾(suffix)、开头处的多余字符(prefixOverrides)、结尾处的多余字符(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语句
实现遍历list。
<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.ys.po.User">
select * from user
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
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运行原理
MyBatis的核心组件:
SqlSessionFactoryBuilder:生成SqlSessionFactory
SqlSessionFactory:生成SqlSession
SqlSession:发送SQL去执行,并返回结果
MyBatis的运行包括两部分:一。读取配置文件,构建SqlSessionFactory对象。二。SqlSession的执行过程。以下分析较为复杂的第二部分。
先看一个问题:使用MyBatis时需要定义Mapper接口和SQL映射文件,这里的Mapper只是个接口,他是如何执行的?
答案就是动态代理。
动态代理有两种实现:1是JDK通过反射提供的动态代理;2是CGLIB动态代理。区别:JDK代理需要提供接口,CGLIB不需要;MyBatis中这两种方式都使用了。
一般来说实现代理,代理的是一个目标类。但是MyBatis使用时并没有类,只有一个接口。这也是可以的。JDK动态代理可以直接生成 一个接口的实现类。
JDK动态代理的使用:
//首先是实现InvocationHandler接口,实现invoke方法
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable {
//method是要被代理执行的方法
//args是要传入的参数
//被代理类方法的执行:
Object result = method.invoke(args);
//可加入其他逻辑
}
//二是要生成代理类,使用Proxy.newProxyInstance()
//这里的传入的第三个参数this应该是一个实现InvocationHandler接口的类的对象
//这个例子是还是先有了一个目标类,但只有接口的情况也是可以的
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");
}
}
动态代理实现原理
JDK动态代理:
利用反射实现;
只依赖JDK本身,不需要依赖外部库;JDK比外部库更加可靠;
需要被代理类实现接口;
代码实现简单一些。
CGLIB动态代理:
基于ASM实现;
不需要目标类实现接口;
不能代理final类,因为不能生成其子类;
高性能。
反射:
涉及Class,Field,Method,Constructor等类
SqlSessionFactory的构建过程,略
主要是读取配置文件到Configuration类,在通过SqlSessionFactoryBuilder类生成