Mybatis
一、工作原理
1)核心
原理详解:
MyBatis应用程序根据XML配置文件创建SqlSessionFactory,SqlSessionFactory在根据配置,配置来源于两个地方,一处是配置文件,一处是Java代码的注解,获取一个SqlSession。SqlSession包含了执行sql所需要的所有方法,可以通过SqlSession实例直接运行映射的sql语句,完成对数据的增删改查和事务提交等,用完之后关闭SqlSession。
2)执行流程图
加载sqlmapconfig.xml,通过sqlsessionfactorybuilder,构建sqlsesionfactroy对象.由它构建sqlsession提供增删改查等操作数据库的方法.
1.sqlMapConfig.xml:mybatis全局配置文件,配置了数据源,事务等mybatis的运行环境,与spring整合后由spring的配置文件接管。mapper.xml映射文件(配置sql语句等)
2.sqlSessionFactory(会话工厂):创建sqlSession(根据配置文件创建)。
3.sqlSession:操作数据库(crud):是一个面向用户的接口
4.Executor(执行器):SqlSession内部通过该执行器操做数据库(是一个接口:分为基本执行器和缓存执行器)
5.mapped Statement:(底层封装对象):对操作数据库存储封装:包括sql语句,输入参数,输出结果类型进行封装。
mapped Statement的主要成员
StatementHandler 封装了JDBC Statement操作,负责对JDBCstatement 的操作,如设置参数等
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
MappedStatement MappedStatement维护一条<select|update|delete|insert>节点的封装
以上主要成员在一次数据库操作中基本都会涉及,在SQL操作中重点需要关注的是SQL参数什么时候被设置和结果集怎么转换为JavaBean对象的,这两个过程正好对应StatementHandler和ResultSetHandler类中的处理逻辑。
3)优缺点
优点:
1、灵活
2、解除sql与程序代码的耦合
3、提供映射标签,支持对象与数据库的orm字段关系映射
4、提供对象关系映射标签
5、提供xml标签,支持编写动态sql。
1、编写SQL语句时工作量很大,尤其是字段多、关联表多时,更是如此。
2、SQL语句依赖于数据库,导致数据库移植性差,不能更换数据库。
3、二级缓存机制不佳
使用jdbc开发时,和mybatis相比的不足
1,数据库连接,使用时就创建,不使用就释放,对数据库进行频繁连接开关和关闭,造成数据库资源浪费,影响数据库的性能
解决:使用数据库连接池管理数据库的连接
2,sql语句使用硬编码在java程序中,修改sql语句,就需要重新编译java代码,不利于系统维护
解决:把sql语句放在xml配置文件中,修改sql语句也不需要重新编译java代码
3,向预编译语句PreparedStatement中设置参数,对占位符位置和设置参数值,硬编码,修改sql语句也不需要重新编译java代码
解决:把sql语句和占位符设置参数值放在xml配置文件中
4,从result中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码
解决:将查询的结果集,自动映射成 java对象
和jdbc比较:
mybatis抽离出数据库的连接,关闭的操作.抽离了sql语句,并且可以自动的进行参数的设置,封装结果集.
2.mybatis跟hibante的比较, 优缺点.
1.hibernate是全自动,而mybatis是半自动。
hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。
而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。
2. hibernate数据库移植性远大于mybatis。
hibernate通过它强大的映射结构和hql语言,大大降低了对象与数据库(Oracle、MySQL等)的耦合性,而mybatis由于需要手写sql,因此与数据库的耦合性直接取决于程序员写sql的方法,如果sql不具通用性而用了很多某数据库特性的sql语句的话,移植性也会随之降低很多,成本很高。
3. mybatis相比hibernate需要关心很多细节
hibernate配置要比mybatis复杂的多,学习成本也比mybatis高。
4. sql直接优化上,mybatis要比hibernate方便很多
由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。
而hibernate的sql很多都是自动生成的,无法直接维护sql;
虽有hql,但功能还是不及sql强大,见到报表等变态需求时,hql也歇菜,也就是说hql是有局限的;
hibernate虽然也支持原生sql,但开发模式上却与orm不同,需要转换思维,因此使用上不是非常方便。
总之写sql的灵活度上hibernate不及mybatis。
和hibernate比较:
- 性能:mybatis较hibernate高
- sql灵活性:mybatis较hibernate高
- 配置文件:mybatis较hibernate多(维护困难)
- 数据库的无关性:mybatis较hibernate低
4)编程步骤
1、加入jar包
2、创建conf.xml
3、创建xxxMapper.xml,要在conf.xml注册
4、建包bean,Mapper
5、测试
public staticvoid main(String[] args) throws IOException {
Stringresource = "conf.xml";
//加载mybatis 的配置文件(它也加载关联的映射文件)
Reader reader= Resources.getResourceAsReader(resource);
//构建sqlSession 的工厂
SqlSessionFactorysessionFactory = new SqlSessionFactoryBuilder().build(reader);
//创建能执行映射文件中sql 的sqlSession
SqlSessionsession = sessionFactory.openSession();
//映射sql 的标识字符串
Stringstatement = "com.atguigu.mybatis.bean.userMapper"+".selectUser";
//执行查询返回一个唯一user 对象的sql
User user =session.selectOne(statement, 1);
System.out.println(user);
}
CUD操作:
① 创建SqlSessionFactory
② 通过SqlSessionFactory创建SqlSession
③ 通过sqlsession执行数据库操作
④ 调用session.commit()提交事务
⑤ 调用session.close()关闭会话
二、sqlMapConfig.xml
SqlMapConfig,xml解析
文件中的配置项是有顺序的,按照官方图来配.
configuration
- properties
- settings
- typeAliases
- typeHandlers
- objectFactory
- plugins
- environments
* envioronment
* transactionManager
* dataSource
- databaseIdProvider
- mappers
1.properties标签:
可以引用一些properties的资源文件。注意:不建议在properties标签里添加属性,可能会覆盖掉其他相同名称的属性值。
2.Setting标签:
配置全局参数:主要是跟Mybaties运行相关的一些参数
3.typeAliases
别名定义。在mybatis中的resultType和parameterType的类型有可能是自定义的pojo类型,地址很长,不利于开发,可以使用别名定义。
批量别名定义:在其中使用package标签,name为表名,别名为类名,首字母不区分大小写。
4.类型处理器
jdbc类型与Java类型之间的相互转换,mybatis提供了许多转换类型,因此一般不需要我们手动配置。
5.mapper
class属性加载的是通过mapper接口加载映射文件,但需要遵循一些规范:需要将mapper接口类名和mapper.xml映射文件名称一致,且在同一个目录当中。
mapper批量加载:遵循上述规范,用package标签
<!-- resource:表示从classpath下加载 -->
<properties resource="db.properties">
<property name="jdbc.driver"value="com.mysql.jdbc.Driver"/>
</properties>
settings: 全局参数设置
设置延迟加载:
<settings>
<!-- 开启延迟加载,默认值为true-->
<settingname="lazyLoadingEnabled" value="true" />
<!--设置积极的懒加载,默认值是true-->
<setting name="aggressiveLazyLoading"value="false" />
<settings>
typeAliases:类型别名
<!-- 自定义别名 -->
<typeAliases>
<!-- type:指定java对象类型 -->
<!-- alias:给java对象取得别名,对大小写不敏感-->
<typeAlias type="com.aric.mybatis.po.User"alias="user"/>
<!-- 扫描包的方式,别名就是包下类的类名,不区分大小写-->
<package name="com.aric.mybatis.po"/>
</typeAliases>
<typeAliases>
<typeAlias alias="Song" type="com.yrw.crawler.model.Song"/>
</typeAliases>
<plugins>
<plugininterceptor="com.github.pagehelper.PageHelper">
<propertyname="dialect" value="mysql"/>
</plugin>
</plugins>
<environmentsdefault="development">
<environmentid="development">
<transactionManagertype="JDBC" />
<dataSourcetype="POOLED">
<propertyname="driver" value="com.mysql.jdbc.Driver" />
<propertyname="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<propertyname="username" value="root" />
<propertyname="password" value="" />
</dataSource>
</environment>
</environments>
mappers:
<mappers>
<!-- resource:从classpath下加载sql映射文件-->
<!-- <mapper resource="sqlmap/User.xml"/>
<mapper resource="mapper/User.xml"/> -->
<!-- 指定mapper接口的类路径 -->
<!-- 要求:1.mapper接口 和sql的映射文件在同一个目录下,2.mapper接口的类名和映射文件的名称要一致-->
<!-- <mapper class="com.aric.mybatis.mapper.UserMappser"/> -->
<!--包扫描-->
<package name="com.aric.mybatis.mapper" />
</mappers>
三、映射文件
mapper代理的方式(对原始dao的优化), 基于注解或者基于xml文档mapper,只需要写接口,不需要写实现类,实现类由mybatis框架自动创建.
使用步骤:
1.程序员需要编写mapper.xml文件
2.编写mapper接口需要依据规范:
1. sql的映射文件中的namespace要和Mapper接口中的类路径(全限定名)一致
2. sql的映射文件中的sql的id要和mapper接口中的方法的名称一致
3. sql的映射文件中的parameterType要和mapper接口中的方法的参数类型一致
4. sql的映射文件中的resultType要和mapper接口中的方法的返回值数据类型一致
3.获取自动生成的实现类对象
sqlSession.getMapper(接口名.class);
<!-- namespace:命令空间,类似包-->
<mappernamespace="test">
<!-- 添加用户 -->
<!-- parameterType指定java对象,获取值时#{}中必须是java对象中的属性名 -->
<!-- keyProperty:查询到的主键,放入到User对象中的id属性 -->
insert操作时,要注意主键 主键生成策略,要设置useGeneraterKey= true,和 keyProperty="id",指定哪个是主键
<insert id="addUser" parameterType="User"
useGeneratedKeys="true" keyProperty="id">
insert into user(userName,userAge,userAddress)
values(#{userName},#{userAge},#{userAddress})
</insert>
<!-- 删除用户 -->
<delete id="deleteUserById"parameterType="int">
delete from user where id = #{id}
</delete>
<!-- 更新用户 -->
<update id="updateUserById"parameterType="user">
update user set birthday=#{birthday},sex=#{sex},address=#{address} whereid=#{id}
</update>
<!-- id:代表sql语句的唯一标识-->
<!-- resultType:查询结果集的类型 -->
<!-- parameterType:输入参数的数据类型,通过parameterType接收参数,进行动态sql生成。运用ognl表达式-->
<!-- #{id}:用于接收数据,代表一个占位符,表示将外部传递过来的数据映射到sql语句中 -->
<!-- #{}中的变量可以是任意的(前提条件是:输入参数的数据类型是简单的类型) -->
<select id="getUserById" parameterType="int"resultType="com.aric.mybatis.po.User" >
select * from user where id=#{idd}
</select>
<!-- 如果查询的结果是列表,那么resultType只需指定列表中元素的类型 -->
<select id="getUserByUsername"parameterType="String"resultType="com.aric.mybatis.po.User">
<!-- ${value}表示sql串的拼接,${value}中变量必须是value(如果输入的是简单类型)
注意点
#{xxx}表示一个占位符,jdbc中的?通过#{xxx} 可以将外部传递过来映射到sql语句中,可以有效的防止sql注入.
xxx表示一个sql串的拼接,不可以有效防止sql注入.如果是{xxx},输入的参数类型是简单类型,那么${xxx}中的xxx必须是value.
-->
select * from user where username like "%${value}%"
</select>
<select id="selectUserOrders_details_items" resultMap="resultMap_user_orders_details_items">
SELECT
u.id AS user_id,
u.username,
ord.id AS order_id,
ord.number,
de.id AS detail_id,
i.id AS item_id,
i. NAME
FROM
USER u
LEFT JOIN orders ord ON u.id = ord.user_id
LEFT JOIN orderdetail de ON de.orders_id = ord.id
LEFT JOIN items i ON de.items_id = i.id
</select>
</mapper>
输入映射
自定义包装类型,可以将很多pojo类以及扩展类定义在包装类型中,根据需求而定
输出映射
1.resultType
①:使用该属性映射,只有查询出来的列名与pojo属性名一致,才可以映射成功
②:如果查询出来的列名与pojo属性名全都不一致,则不会创建映射的pojo对象,但只要有一个列与pojo属性名称一致,就会创建pojo对象
2.resultMap
如果查询出来的列名与pojo属性名不一致却还要与pojo映射,可以使用resultMap
首先要定义resultMap与pojo属性的对应关系
orm格式转换:通过设置resultMap和ResultType,将数据库中的记录转换为代码的bean对象。得到list或者对象。返回list,必须要配置resultMAp.
resultMap做复杂的映射配置(多表查询).
解决字段名与实体类属性名不相同的冲突
通过<resultMap>
<select id="selectOrderResultMap"parameterType="int" resultMap="orderResultMap">
select * from orders where order_id=#{id}
</select>
<resultMap type="_Order"id="orderResultMap">
<id property="id" column="order_id"/>
<result property="orderNo"column="order_no"/>
<result property="price"column="order_price"/>
</resultMap>
一对一查询
使用association标签将关联的信息映射到单个Java对象中,使用collection将关联信息映射到对象的集合当中。
<!--
方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
封装联表查询的数据(去除重复的数据)
select * from class c, teacher t wherec.teacher_id=t.t_id and c.c_id=1
-->
<select id="getClass"parameterType="int" resultMap="ClassResultMap">
select * fromclass c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
</select>
<resultMap type="_Classes"id="ClassResultMap">
<idproperty="id" column="c_id"/>
<resultproperty="name" column="c_name"/>
<associationproperty="teacher" column="teacher_id"javaType="_Teacher">
<idproperty="id" column="t_id"/>
<resultproperty="name" column="t_name"/>
</association>
</resultMap>
<!--
方式二:嵌套查询:通过执行另外一个SQL 映射语句来返回预期的复杂类型
SELECT * FROM class WHERE c_id=1;
SELECT * FROM teacher WHERE t_id=1 //1 是上一个查询得到的teacher_id 的值
-->
<select id="getClass2"parameterType="int" resultMap="ClassResultMap2">
select * from class where c_id=#{id}
</select>
<resultMap type="_Classes"id="ClassResultMap2">
<idproperty="id" column="c_id"/>
<resultproperty="name" column="c_name"/>
<associationproperty="teacher" column="teacher_id"javaType="_Teacher"
select="getTeacher">
</association>
</resultMap>
<select id="getTeacher"parameterType="int" resultType="_Teacher">
SELECT t_idid, t_name name FROM teacher WHERE t_id=#{id}
</select>
映射一对多关系
<!--
方式一: 嵌套结果: 使用嵌套结果映射来处理重复的联合结果的子集
SELECT * FROM class c, teacher t,students WHERE c.teacher_id=t.t_id AND c.C_id=s.class_id AND c.c_id=1
-->
<select id="getClass3"parameterType="int" resultMap="ClassResultMap3">
select * fromclass c, teacher t,student s where c.teacher_id=t.t_id and c.C_id=s.class_idand c.c_id=#{id}
</select>
<resultMap type="_Classes"id="ClassResultMap3">
<idproperty="id" column="c_id"/>
<resultproperty="name" column="c_name"/>
<associationproperty="teacher" column="teacher_id"javaType="_Teacher">
<idproperty="id" column="t_id"/>
<resultproperty="name" column="t_name"/>
</association>
<!-- ofType指定students 集合中的对象类型 -->
<collectionproperty="students" ofType="_Student">
<idproperty="id" column="s_id"/>
<resultproperty="name" column="s_name"/>
</collection>
</resultMap>
<!--
方式二:嵌套查询:通过执行另外一个SQL 映射语句来返回预期的复杂类型
SELECT * FROM class WHERE c_id=1;
SELECT * FROM teacher WHERE t_id=1 //1 是上一个查询得到的teacher_id 的值
SELECT * FROM student WHERE class_id=1//1 是第一个查询得到的c_id 字段的值
-->
<select id="getClass4"parameterType="int" resultMap="ClassResultMap4">
select * fromclass where c_id=#{id}
</select>
<resultMap type="_Classes"id="ClassResultMap4">
<idproperty="id" column="c_id"/>
<resultproperty="name" column="c_name"/>
<associationproperty="teacher" column="teacher_id"javaType="_Teacher"
select="getTeacher2"></association>
<collectionproperty="students" ofType="_Student"column="c_id" select="getStudent"></collection>
</resultMap>
<select id="getTeacher2"parameterType="int" resultType="_Teacher">
SELECT t_idid, t_name name FROM teacher WHERE t_id=#{id}
</select>
<select id="getStudent"parameterType="int" resultType="_Student">
SELECT s_idid, s_name name FROM student WHERE class_id=#{id}
</select>
多对多映射
<!-- resultMap:定义查询用户关联查询订单关联查询订单明细及商品信息 -->
<resultMap type="user" id="resultMap_user_orders_details_items">
<id column="user_id" property="id" />
<result column="username" property="username"/>
<!-- 一个用户对应多个订单 -->
<collection property="orders" ofType="com.aric.mybatis.po.Orders">
<id column="order_id" property="id" />
<result column="number" property="number" />
<!-- 一个订单对应多个订单明细 -->
<collection property="orderdetail" ofType="com.aric.mybatis.po.OrderDetail">
<id column="detail_id" property="id" />
<!-- 一个订单明细对应一个商品 -->
<association property="items" javaType="com.aric.mybatis.po.Items">
<id column="item_id" property="id" />
<result column="name" property="name" />
</association>
</collection>
</collection>
</resultMap>
四、缓存
正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持
1. 一级缓存: 基于PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session.flush 或 close 之后,该Session 中的所有 Cache 就将清空。
2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于
其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
3. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了
C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
一级缓存:session(默认使用的)
1、同一个session,并且session没有close或者clearcache
2、查询条件一样
3、没有执行过增删改操作
二级缓存:Mapper(需要手动开启)
二级缓存的配置
1. 开启二级缓存中开关
<!--二级缓存总开关-->
<setting name="cacheEnabled" value="true">
2. 在mapper映射文件中开启二级缓存
<!--开启二级缓存,默认使用了PerpetualCache-->
<cache/>
<cache
eviction="FIFO" //回收策略为先进先出
flushInterval="60000" //自动刷新时间60s
size="512" //最多缓存512 个引用对象
readOnly="true"/> //只读
3. 禁止使用缓存
useCache=”false”
4. 刷新缓存
select语句默认是false,增删改语句默认是true
flushCache="true"
五、动态SQL
1.主要是where标签与if标签的综合使用,进行灵活的判断后动态拼接sql语句,where标签会自动将第一个and去掉。
2.定义sql片段:可以将相同的sql片段提取出来,这样其他的语句就可以引用该sql,在mapper.xml文件中单独使用sql标签定义。注意:相对于单表定义可重用性才高,不要在其中定义where标签。引用sql片段用include标签。
3.foreach标签可以遍历组装sql语句
collection属性为集合属性名
items为集合属性名称
open为拼接开始语句,close为拼接结束语句,separator为拼接集合属性的连接sql
MyBatis中用于实现动态SQL的元素主要有:
1、if和where
2、choose(when,otherwise)
3、trim
4、set
5、foreach
下面将逐一进行介绍。
if
<!-- 根据用户id和用户名称查询,需要进行判断,如果id为空,就以名称查询,如果名称为空,就以id查询,如果都为空,查询所有 -->
<select id="getUserByIdOrUsername" parameterType="user" resultType="user">
select * from user
<!-- where:可以去掉多余的and,拼接where条件 -->
<where>
<if test="id != null">
and id=#{id}
</if>
<if test="username != null and username !=''">
and username like '%${username}%'
</if>
</where>
</select>
choose, when, otherwise
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
trim, where, set
<!-- 更新用户 -->
<update id="updateUser" parameterType="user">
update user
<!-- set:更新并且可以去掉多余的"," -->
<set>
<if test="address != null and address != ''">
address =#{address},
</if>
<if test="sex != null and sex != ''">
sex =#{sex},
</if>
<if test="username != null and username != '' ">
username =#{username}
</if>
</set>
where id=#{id}
</update>
<!--去掉多余的前缀-->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
<!--去掉多余的后缀-->
<trim prefix="SET" suffixOverrides=",">
...
</trim>
foreach
<!-- 查询id在一个范围的用户 -->
<select id="getUserListByForEach" parameterType="QueryVo" resultType="user">
select * from user
<where>
<!-- collection:queryvo中的集合属性ids -->
<!-- open:表示开始的sql串 -->
<!-- close:表示结束的sql串 -->
<!-- item:要遍历的变量 -->
<!-- separator:循环遍历时,指定分割的字符 -->
<foreach collection="ids" open="id in(" close=")" item="id" separator=",">
`#{id}`
</foreach>
</where>
</select>
六、Mybatis整合spring
1. 创建工程
2. 加入jar spring的包 mybatis的包依赖包 数据库驱动 数据连接池 整合包 日志包
3. mybatis配置文件
<configuration>
<!--全部交给spring管理,但是文件必须存在-->
</configuration>
4. spring的配置文件
<!-- 配置db.properties数据源 -->
<context:property-placeholderlocation="classpath:db.properties" />
<!-- 配置数据库连接池 -->
<bean id="dataSource"class="org.apache.commons.dbcp.BasicDataSource">
<!-- 配置连接池属性 -->
<propertyname="driverClassName" value="${jdbc.driver}" />
<property name="url"value="${jdbc.url}" />
<propertyname="username" value="${jdbc.username}" />
<propertyname="password" value="${jdbc.password}" />
<propertyname="maxActive" value="10" />
<propertyname="maxIdle" value="5" />
</bean>
<!-- 配置sqlSessionFactory -->
<bean id="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入mybatis配置文件-->
<propertyname="configLocation"value="classpath:mybatis/SqlMapConfig.xml"></property>
<!-- 注入连接池 -->
<propertyname="dataSource" ref="dataSource"></property>
</bean>
<!-- mapper扫描方式生成代理对象 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定扫描的包 :1.自动批量生成代理对象(接口的类名首字母小写) 2.加载mapper映射文件 -->
<propertyname="basePackage"value="com.aric.mybatis.mapper"></property>
<propertyname="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<!-- 4. 事务管理 : DataSourceTransactionManager-->
<beanid="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<propertyname="dataSource" ref="dataSource" />
</bean>
<!--5. 使用声明式事务 -->
<tx:annotation-driventransaction-manager="txManager" />
</beans>
5、通过逆工程生成POJO、Mapper和xxxMapper.xml
6. 数据库、日志的配置文件
上一篇: SpringMVC
下一篇: J2EE学习笔记 用JSON做前后端分离
推荐阅读