介绍
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。 MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatement、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。
使用原生jdbc出现的问题
1、 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
2、 Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
3、 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
Mybatis架构
Mybatis配置
SqlMapConfig.xml: mybatis的全局配置文件,配置了mybatis的运行环境等信息。
Mapper.xml: sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在sqlMapConfig.xml中加载。
#{}占位符:占位
如果传入的是基本类型,那么#{}中的变量名称可以随意写
如果传入的参数是pojo类型,那么#{}中的变量名称必须是pojo中的属性.属性名(user.username)
${}拼接符:字符串原样拼接(有sql注入的风险)
如果传入的是基本类型,那么${}中的变量名必须是value
如果传入的参数是pojo类型,那么${}中的变量名称必须是pojo中的属性.属性名(user.username)
注意:使用拼接符有可能造成sql注入,在页面输入的时候可以加入校验,不可输入sql关键字,不可输入空格
注意:如果是取简单数量类型的参数,括号中的值必须为value
例: select * from user where username like '%${value}%'
可以这样写来解决sql注入:select * from user where username like '%’ #{name}’%'(开发经常使用)
入门
注意本程序只是为了理解mybatis的使用,实际开发中可能不这样使用
SqlMapConfig.xml配置:
(并不固定,可以写别的名称)mybatis基本配置
<?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>
<!-- 和spring整合后 environments配置将废除-->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理-->
<transactionManager type="JDBC" />
<!-- 数据库连接池-->
<dataSource type="POOLED ">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!—关联映射-->
<mappers>
<mapper resource="sqlmap/User.xml"/>
</mappers>
</configuration>
经测验映射文件最好和核心配置文件在一个文件夹下
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
Mapper配置文件
配表与pojo关系,以及sql
<!-- 根据id获取用户信息 -->
<select id="findUserById" parameterType="int " resultType="cn.itcast.mybatis.po.User" >
select * from user where id = #{id}
</select>
id: sql语句的唯一标识,与dao接口中的方法名一致
parameterType:定义输入到sql中的映射类型(请求参数),#{id}表示使用preparedstatement设置占位符号并将输入变量id传到sql。id 可以用任意字符代替并不固定
resultType:定义结果映射类型(返回值)。
注意:当返回值是List集合时他的类型就是泛型的类型
mybatis支持别名:
别名 | 映射类型 |
_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 |
map | Map |
动态Sql
If
通过mybatis提供的各种标签方法实现动态拼接sql
原始sql语句:
SELECT * FROM USER WHERE username LIKE '%王%' AND sex= ’2’
Mybatis中简单的sql语句:
<select id="findUserByNameAndSex" parameterType="user" resultType="user">
SELECT * FROM USER WHERE username LIKE '%${username}%' AND sex=#{sex}
</select>
trim
<trim prefix="(" suffix=")" prefixOverrides="and" ></trim>
前缀'and' 被'(' 替换
prefix:前缀覆盖并增加其内容 不写的话默认替换为空
suffix:后缀覆盖并增加其内容 不写的话默认替换为空
prefixOverrides:前缀判断的条件
suffixOverrides:后缀判断的条件
使用动态sql语句:
where
<select id="findUserByNameAndSex" parameterType="user" resultType="user">
SELECT * FROM USER
<where><!-- 他能自动判断将where关键字后的and去掉 -->
<if test="username!=null and username!=''">
AND username LIKE '%${username}%'
</if>
<if test="sex!=null and sex!=''">
AND sex=#{sex}
</if>
</where>
</select>
<where> 可以自动去除sql语句where关键字后的and关键字
Foreach
向sql传递数组或List, mybatis使用foreach解析,可以做批量处理
Sql语句:
SELECT * FROM USER WHERE id IN (1,10,16)
使用foreach赋值:
<select id="findUserByIds" parameterType="queryVo" resultType="user">
SELECT * FROM USER
<where>
<foreach collection="ids" open="id IN (" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
foreach:循环传入的集合参数
collection:传入的集合的变量名称(要遍历的值)
item:每次循环将循环出的数据放入这个变量中
open:循环开始拼接的字符串
close:循环结束拼接的字符串
separator:循环中拼接的分隔符
批量修改实例:
sql:
UPDATE rc_notify_queue SET queue_status = 'E',execute_time=NOW() WHERE notify_queue_Id IN (3,22,11)
<!-- 批量修改 -->
<update id="updateByList" parameterType="java.util.List" >
UPDATE rc_notify_queue SET queue_status = 'E',execute_time=NOW()
<where>
<foreach collection="list" open="notify_queue_Id IN (" close=")" item="notify" separator=",">
#{notify.notifyQueueId}
</foreach>
</where>
</update>
一个大神朋友写的批量修改:
choose
if标签是与(and)的关系,而 choose 是或(or)的关系。
有时候我们并不想应用所有的条件,而只是想从多个选项中选择一个。而使用if标签时,只要test中的表达式为 true,就会执行 if 标签中的条件。MyBatis 提供了 choose 元素。choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。
sql片段
Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的
将where条件抽取出来:
<sql id="user_where">
<where>
<if test="username!=null and username!=''">
AND username LIKE '%${username}%'
</if>
</where>
</sql>
引用:
<select id="findUserByNameAndSex" parameterType="user" resultType="user">
SELECT * FROM USER
<include refid="user_where"/>
</select>
注意:也可在sql标签中也可以只放if标签
<if test="_parameter != null" >
_parameter : 参数的引用
关联查询:
多表查询返回的数据是两个表中的数据这样就出现了数据封装的问题
解决方案一:创建一个有两个表的数据类
<!-- 一对一:自动映射 -->
<!-- 一对一:自动映射 -->
<select id="findOrdersAndUser" resultType="customOrders">
SELECT * FROM USER a , orders b WHERE a.id = b.user_id
</select>
和普通方法是一样的,只是要多创建一个po类
解决方案二:不需要新建po类只要在映射文件中配置:
<resultMap type="orders" id="orderAndUserResultMap">
<id column="id" property="id"/>
<result column="userId" property="user_id"/>
<result column="number" property="number"/>
<result column="createtime" property="createtime"/>
<result column="note" property="note"/>
<!-- 封装对象属性 -->
<association property="user" javaType="com.itheima.pojo.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="sex" property="sex"/>
<result column="address" property="address"/>
<result column="birthday" property="birthday"/>
</association>
</resultMap>
使用:
<select id="findOrdersAndUser2" resultMap="orderAndUserResultMap">
SELECT * FROM USER a , orders b WHERE a.id = b.user_id
</select>
Type: po类的权限定名,这里使用的是别名
Id: 主键
Result: 普通属性
Column: po类属性
Property: 表字段名
Association: 引用数据类型
Property: 在po类中的名称
JavaType: 全限定名
分两块,第一块使用resultMap标签封装数据,第二块在select中使用resultMap引用封装好的数据;
这种放法优于第一种方法,第一种方法再写po类增加了工作量,也不便于以后对数据的使用,也不能表现表与表之间的关系
封装map集合:
返回结果集类型: resultType="java.util.Map"
查询后返回的结果:List<Map>
将数据库中每条数据:字段和值按照 key(字段名),value(字段值) 封装到hashMap集合中
然后在把Map集合封装到Llist集合中
将一条数据封装成map集合:
使用resultMap封装数据
<resultMap type="java.util.Map" id="orderAndUserResultMap"> <id column="cid" property="cid"/> <result column="cust_name" property="cust_name"/> <result column="cust_type" property="cust_type"/> <result column="cust_phone" property="cust_phone"/> <result column="cust_address" property="cust_address"/> <result column="custlink_user" property="custlink_user"/> </resultMap> <select id="findCustList" parameterType="cn.itheima.pojo.QueryVo" resultMap="orderAndUserResultMap"> SELECT * FROM s_cust </select>
Column就是map集合的key Property数据库传来的数据注意:property值对应数据库中的字段值这种方式只能获取一条数据
Spring整合mybatis
SqlMapConfig.xml配置:
<typeAliases>
<!-- 包扫描 -->
<package name="com.mybatis.pojo"/>
</typeAliases>
<!-- 关联映射 -->
<mappers>
<mapper resource="User.xml"/>
<!-- 包扫描 -->
<package name="com.mybatis.dao"/>
</mappers>
其余交个spring管理
ApplicationContext.xml
配置dataSource
配置sqlSessionFactory
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 指定mybatis核心配置文件 -->
<property name="configLocation" value="classpath:SqlMapConfig.xml"></property>
<!-- 指定会话工厂使用的数据源 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
原生Dao实现:原生Dao实现和普通类的配置一样
Mapper接口代理:
方式一:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 配置mapper接口的全路径名称 -->
<property name="mapperInterface" value="com.mybatis.dao.UserMapper"></property>
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
注意:
Bean标签中class属性是mybatis提供的接口代理对象所以是固定的
name="mapperInterface"和name="sqlSessionFactory" 代理对象中的属性也是固定的
方式二:包扫描
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.mybatis.dao"></property>
</bean>
注意:
使用包扫描的方式批量引入Mapper扫描后引用的时候可以使用类名,首字母小写.
指定要扫描的包的全路径名称,如果有多个包用英文状态下的逗号分隔