MyBatis的相关概念
1、特点:MyBatis是一个基于ORM的半持久层轻量级框架;
a、ORM:对象/关系数据库映射(Object/Relation Mapping),实体类与数据库形成对应关系,操作实体类等于操作数据库表
b、半自动:手动编写SQL语句,可以优化SQL语句比全自动不可优化的SQL语句执行效率更高
c、轻量级:启动过程中所需资源的较少
2、使用方式:
a、XML
b、注解
3、历史:iBatis---->MyBatis(Apache---->Gougle)
4、优势:
a、可以优化SQL语句提高执行效率
b、SQL语句与JAVA编码分开,解决了硬编码问题,使其功能边界清晰,一个专注业务,一个专注数据
MyBatis基本应用
1、开发步骤:
a、添加MyBatis的坐标
b、创建数据库表
c、根据数据库表编写实体类
d、编写映射配置文件(SQL、返回值等)
e、编写核心配置文件
f、编写测试类
2、映射配置文件分析
a、<!DOCTYPE......> ----> 映射文件DTD约束头
b、<mapper namespace="userMapeer"></mapper> ----> mapper:跟标签 namespace:命名空间与SQL语句id一起组成唯一标识符
c、<selsect id="findUser" resultType=”....“>......</selsect>、<insert id="saveUser" parameterType="">......</insert>、<update id="upadeUser" parameterType="">......</update>、<delete id="deleteUser" parameterType="">......</delete> ----> SQL操作标签表示:查询操作、添加操作、修改操作、删除操作
d、以上标签中......略写了需要执行的SQL语句,如:select * from user
e、以上标签中的id与命名空间一起组成SQL语句的唯一标识
f、以上标签中resultType表示查询结果对应的返回实体类
g、以上标签中parameterType表示查询所需参数类型,可以是实体类,也可以是基础数据类型,如查询参数类型是实体类则SQL语句中的参数名应与实体类中属性名一致,如查询参数类型是基本参数类型且只有一个参数则SQL语句中参数名可以随便命名
h、以上标签中SQL语句查询参数标识符有两种#{}和${},#{}所用居多, #{}将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号,#{}能够很大程度防止sql注入,${}将传入的数据直接显示生成在sql中,${}无法防止sql注入,一般用于传入数据库对象,例如表名,一次排序使用order by时应使用${},一般能用#{}就不用${}
i、动态SQL标签
1)、if标签:根据实体类的不同取值,使用不同的SQL语句进行查询。在多条件组合查询中会用到,不为空拼接,为空不拼接。【where标签:】不用自己控制where 1=1,MyBtais提供where标签会自动拼接
<select id="findByConditional" parameterType="user" resultType="user">
SELECT * FROM user
<where>
<if test="id != null">
AND id = #{id}
</if>
<if test="username != null">
AND username = #{username}
</if>
</where>
</select>
2)、foreach标签:循环执行sql的拼接操作,多值查询,【collection属性:】表示参数类型,数据传array,集合传list,【opend属性:】表示开始位置,如id in(,【close属性:】表示结束位置,如),【item属性:】表示当前参数名,如id,【separator属性:】表示拼接占位符,如,
<select id="findByIds" parameterType="list" resultType="user">
SELECT * FROM user
<where>
<foreach collection="array" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
3)、SQL语句抽取sql标签-include标签:sql标签抽取重复的SQL语句,include标签使用sql标签,【refid属性】等sql标签id
<sql id="selectUser">
SELECT * FROM user
</sql>
<select id="findByIds" parameterType="list" resultType="user">
<include refid="selectUser"></include>
<where>
<foreach collection="array" open="id in(" close=")" item="id" separator=",">
#{id}
</foreach>
</where>
</select>
3、核心配置文件分析
a、configuration配置
b、properties属性:将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的properties文件(jdbc.properties)【使用EL表达式${}引用文件信息】该标签应该是configuration的第一个子标签
<properties resource="jdbc.properties"></properties>
......
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
c、typeAliases类型别名:为一个JAVA类全限定类名起一个别名,有两种方式,给单个实体类起别名,给多个实体类起别名:
<typeAliases>
<!-- 给单个实体类起别名 -->
<!--<typeAlias type="com.lagou.study.pojo.User" alias="user"></typeAlias>-->
<!-- 通过包给多个实体类起别名,别名就是实体类的类名,且不区分大小写 -->
<package name="com.lagou.study.pojo"/>
</typeAliases>
映射配置文件内容由:
<select id="findAll" resultType="com.lagou.study.pojo.User">
SELECT * FROM user
</select>
变为:
<select id="findAll" resultType="user">
SELECT * FROM user
</select>
Mybatis给基本数据类型提供了默认的别名:
别名 | 实体类 |
---|---|
string | String |
long | Long |
int | Integer |
double | Double |
boolean | Boolean |
… | … |
映射配置文件内容由:
<delete id="deleteUser" parameterType="java.lang.Integer">
DELETE FROM user WHERE id =#{abc}
</delete>
变为:
<delete id="deleteUser" parameterType="int">
DELETE FROM user WHERE id =#{abc}
</delete>
d、typeHandlers类型处理器
e、objectFactory对象工厂
f、plugins插件
g、envionments环境:数据库环境配置,支持多环境配置,default属性表示默认环境id
1)、environment环境变量:id属性指定当前环境名称
~transacrionManager事务管理器:type属性,指定事物管理类型【JDBC】
~dataSource数据源:type属性指定当前数据源类型:
UNPOOLED:不使用连接池,每次被请求的时候打开和关闭联机
POOLED:使用连接池
JNDI:为了能在图EJB或应用服务器这容器中使用,容器可以集中或在外部配置数据源,然后防止一个jNDI上下文引用
h、databaseaprovider数据库厂商标识
i、mappers映射器:加载映射的,有四种加载方式
1)、使用相对于路径的资源引用<mapper resource="/com/lagpu/userMapper.xml" />
2)、使用完全限定资源定位符(URL)<mapper url="file:///var/mapper/userMapper" />
3)、使用映射起借口实现类的完全限定类名<mapper class="com.lagou.mapper.usermapper" />*注解方式可使用,单个加载*
4)、将包内的映射器接口实现全部注册为映射器<package name="com.lagou.mapper" /> *此处需要注意,接口与映射配置文件需要同包同名* *能同时加载注解模式和配置文件模式*
4、Mybatis响应API介绍
a、Resources工具类:配置文件的加载,把配置文件加载成字节输入流
b、SqlSessionFactoryBuilder:解析配置文件,并创建sqlSessionFactory工厂
c、生产sqlSession:sqlSessionFactory.openSession(),默认开启一个事物,但该事物不会自动提交,在进行增删该操作时,要手动提交事物。sqlSessionFactory.openSession(true)使用构造方法可以自动提交
d、sqlSession调用方法:查询所有selectList、查询单个selectOne、添加insert、修改update、删除delete
5、Mybatis的Dao层实现
a、传统开发方式:编写Dao接口、编写DaoImpl实现类
b、代理开发方式:仅编写Dao接口,使用JDK动态代产生代理对象,由代理对象执行原实现类操作,但需要以下规范:
1)、mapper.xml文件中的namespace与mapper接口的全限定名相同
2)、mapper接口方法名和mapper.xml中定义的每个statement的id相同
3)、mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
4)、mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
MyBatis复杂映射开发
1、一对一(使用用户表和订单表作为示例)
用户表和订单表的的关系为,一个用户由多个订单,一个订单只属于一个用户
需求:查询一个订单,与此同时查询出订单所属用户信息
【resultMap标签】:手动配置实体属性与表字段的映射关系,【id属性】:rusultMap标识,【type属性】:要封装到的实体类型:
a、【result标签】:实体类与数据库表字段对应关系----【property标签】:实体类属性----【column标签】:数据表字段名
b、【association标签】:实体类中的实体类属性的配置----【property标签】:实体类属性----【javaType标签】:实体类型:
1)、【resilt标签】::实体类中的实体类属性与数据库表字段对应关系----【property标签】:实体类属性----【column标签】:数据表字段名
<resultMap id="orderMap" type="com.lagou.study.pojo.Order">
<result property="id" column="id"></result>
<result property="orderTime" column="orderTime"></result>
<result property="total" column="total"></result>
<association property="user" javaType="com.lagou.study.pojo.User">
<result property="id" column="uid"></result>
<result property="username" column="username"></result>
</association>
</resultMap>
<select id="findOrderAndUser" resultMap="orderMap">
SELECT * FROM orders o, user u WHERE o.uid = u.id
</select>
2、一对多(使用用户表和订单表作为示例)
用户表和订单表的的关系为,一个用户由多个订单,一个订单只属于一个用户
需求:查询所有用户,与此同时查询出每个用户具有的订单信息
【collection标签】:配置实体类中的集合属性的标签----【propert属性】:实体类中的集合属性名称----【ofType属性】:对应集合的泛型的全路径
<resultMap id="userMap" type="com.lagou.study.pojo.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<collection property="orderList" ofType="com.lagou.study.pojo.Order">
<id property="id" column="oid"></id>
<result property="orderTime" column="ordertime"></result>
<result property="total" column="total"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT u.*, o.id as oid, o.ordertime, o.total FROM user u LEFT JOIN orders o ON o.uid = u.id
</select>
3、多对多(使用用户表和角色表作为示例)
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
需求:查询用户同时查询出该用户的所有角色
<resultMap id="userRoleMap" type="com.lagou.study.pojo.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<collection property="roleList" ofType="com.lagou.study.pojo.Role">
<result property="id" column="id"></result>
<result property="rolename" column="rolename"></result>
<result property="roleDesc" column="roleDesc"></result>
</collection>
</resultMap>
<select id="findUserAndRole" resultMap="userRoleMap">
SELECT * FROM user u LEFT JOIN sys_user_role ur ON ur.userid = u.id
LEFT JOIN sys_role r ON r.id = ur.roleid
</select>
MyBatis注解开发
1、Mybatis常用注解
@Inster:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以约@Result一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
***【@beFore注解】:在被@Before注解的方法会在@Tset之前被执行***
单表增删改查:
@Insert("INSERT INTO user (id, username) VALUES (#{id}, #{username})")
public void addUser(User user);
@Update("UPDATE user SET username = #{username} where id = #{id}")
public void updateUser(User user);
@Select("SELECT * FROM user")
public List<User> selsetUser();
@Delete("DELETE FROM user WHERE id = #{id}")
public void deleteUser(Integer id);
@Results注解、@Result注解、@One注解、@Many注解组合完成复杂关系的配置
注解 | 说明 |
---|---|
@Results | 代替标签resultMap盖住姐中可以使用单个@Results直接,也可以使用@Result集合。使用格式:@Results({@Result{}, @Result{}})或@Results(@Result()) |
@Result | 代替了id标签和result标签@Result中属性介绍:column:数据库列名prepert:需要装配的属性名one:需要使用的@One注解(@Result(one=@One))many:需要使用的@many注解(@Result(many=@Many)) |
@One | 代替assocition 标签 |
@Many | 代替collection标签 |
一对一查询:
@Results({
@Result(property = "id", column = "id"),
@Result(property = "orderTime", column = "orderTime"),
@Result(property = "total", column = "total"),
@Result(property = "user", column = "uid", javaType = User.class, one = @One(select = "com.lagou.study.mapper.IUserMapper.findUserById"))
})
@Select("SELECT * FROM orders")
public List<Order> findOrderAndUser();
@Select("SELECT * FROM user WHERE id = #{id}")
public User findUserById(Integer id);
上述代码执行顺序:
a、执行SELECT * FROM orders 语句
b、将执行结果放入Result中
c、根据@One配置地址执行子查询语句,其中该列对应的cloumn就是子查询的where条件
一对多查询
@Select("SELECT * FROM user")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "roleList", column = "id", javaType = List.class, many = @Many(select = "com.lagou.study.mapper.IOrderMapper.findOrderBuUid")),
})
public List<User> findAll();
@Select("SELECT * FROM orders WHERE uid = #{uid}")
public List<Order> findOrderBuUid(Integer uid);
多对多查询
@Select("SELECT * FROM user")
@Results({
@Result(property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "roleList", column = "id", javaType = List.class, many = @Many(select = "com.lagou.study.mapper.IRoleMapper.findRoleByUserId"))
})
public List<User> findUserAndRole();
@Select("SELECT * FROM sys_role r, sys_user_role ur WHERE ur.roleid = r.id AND ur.userid = #{userId}")
public List<Role> findRoleByUserId(Integer userId);
这里需要对sql语句做好拆解
MyBatis缓存
概念:缓存就是内存中的数据,常常来自对数据库查询结果的报讯,使用缓存,可以避免频繁的与数据库进行交互,进而提高响应速度。内存的读写是很快的
MyBatis提供了对缓存的支持,分为一级缓存和二级缓存,二级缓存要比一级缓存大:
1、一级缓存:是SqlSession级别的缓存,在操作数据库是需要构造sqlSsession对象,在对象中有一个数据结构(HashMap)用户存储缓存数据,不同的sqlSession之间的缓存数据区域(HashMap)是互不影响的:
默认开启状态:在一个SqlSession中,对表根据id进行两次查询,查看发出sql情况。
进行SQL查询时,首先到一级缓存中查询是否有对应的数据,有:直接返回,没有:查询数据库,同时将查询出的结果存到一级缓存中,cachaeKey:statementId,params(当前参数),bountSql(封装需要执行的SQL语句),rowBounds(分页对象),cacheValue:结果对象
两次查询中间做增删改操作,并进行了事物提交,会刷新一级缓存,第二次查询会重新查询数据库,也可使用clearCache手动刷新缓存,目的在于让缓存中存储的是最新信息,避免脏数据
执行测试代码
public void firstLevelCache(){
// 第一次查询USER
User user1 = userMapper.findUserById(1);
// 更新数据
User user = new User();
user.setId(1);
user.setUsername("lilei");
userMapper.updateUser(user);
sqlSession.commit();//提交事物
// 第二次查询USER
User user2 = userMapper.findUserById(1);
//sqlSession.clearCache();//手动刷新缓存
// 第三次查询USER
User user3 = userMapper.findUserById(1);
System.out.println(user1 == user2);
Sys
}
执行结果
10:45:59,229 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@782859e]
10:45:59,234 DEBUG findUserById:159 - ==> Preparing: SELECT * FROM user WHERE id = ?
10:45:59,327 DEBUG findUserById:159 - ==> Parameters: 1(Integer)
10:45:59,380 DEBUG findUserById:159 - <== Total: 1
10:45:59,383 DEBUG updateUser:159 - ==> Preparing: UPDATE user SET username = ? where id = ?
10:45:59,385 DEBUG updateUser:159 - ==> Parameters: lilei(String), 1(Integer)
10:45:59,387 DEBUG updateUser:159 - <== Updates: 1
10:45:59,389 DEBUG JdbcTransaction:70 - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@782859e]
10:45:59,510 DEBUG findUserById:159 - ==> Preparing: SELECT * FROM user WHERE id = ?
10:45:59,511 DEBUG findUserById:159 - ==> Parameters: 1(Integer)
10:45:59,514 DEBUG findUserById:159 - <== Total: 1
false
true
2、二级缓存:是mapper(namespace)级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的
手动开启配置:配置文件中进行配置,基于配置文件形式的sql在mapper未见中中添加cache标签,基于注解形式的sql在接口文件中添加注解@CacheNamespace
二级缓存缓存的不是对象,是对象中的数据。二级缓存的方式是在第二次查询的时候底层重新创建类User对象将缓存的数据放入对象中,由于二级换的戒指多样,不一定只存在缓存中,也有可能存在硬盘中,因此再取缓存的话,需要反序列化,所以MyBatis中的pojo都要实现Serializablc接口
useCache:用来设置是否禁用二级缓存的。基于配置文件形式的SQL,在statement中配置useCache=false可以禁用当前select语句的二级缓存,默认情况是true,基于注解形式的SQL,在对应的方法上添加@Options(useCache=fasle)同样其默认值为true
flushCache:刷新缓存,默认情况下为true
执行测试代码:
public void secondLevelCache(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);
//第一次请求查询
User user1 = mapper1.findUserById(1);
//清空一级缓存,以便查看二级缓存是否生效
sqlSession1.close();
//第二次请求查询
User user2 = mapper2.findUserById(1);
System.out.println(user1 == user2);
}
执行结果:
10:40:02,774 DEBUG IUserMapper:62 - Cache Hit Ratio [com.lagou.study.mapper.IUserMapper]: 0.0
10:40:02,793 DEBUG JdbcTransaction:137 - Opening JDBC Connection
10:40:03,274 DEBUG PooledDataSource:406 - Created connection 379303133.
10:40:03,275 DEBUG JdbcTransaction:101 - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@169bb4dd]
10:40:03,279 DEBUG findUserById:159 - ==> Preparing: SELECT * FROM user WHERE id = ?
10:40:03,373 DEBUG findUserById:159 - ==> Parameters: 1(Integer)
10:40:03,438 DEBUG findUserById:159 - <== Total: 1
10:40:03,452 DEBUG JdbcTransaction:123 - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@169bb4dd]
10:40:03,453 DEBUG JdbcTransaction:91 - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@169bb4dd]
10:40:03,454 DEBUG PooledDataSource:363 - Returned connection 379303133 to pool.
10:40:03,480 DEBUG IUserMapper:62 - Cache Hit Ratio [com.lagou.study.mapper.IUserMapper]: 0.5
false
MyBatis自带的二级缓存的底层依然是HashMap
MyBatis再带的二级缓存,无法实现分布式缓存,如想解决该问题,则需找一个分布式的缓存,专门用来缓存数据,这样不同服务器要的缓存数据都往这里存,常用的分布式缓存框架有:redis、memcached、ehcache等等
3、二级缓存整合redis
MyBatis提供了一个针对cache接口的redis实现类,该类存在mybatid.redis包中,源码分析:
无论是MyBatis自带的缓存实现类还是Redis的缓存实现类或者自写的,其实现的都需要实现Cache这个接口,RedisCache给了redis默认配置信息,可以通过配置reids.proprtties对象修改默认配置信息,这个文件的文件名和数据名是固定的:
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0
a、如何完成Redis缓存存取:putObject()、getObject()使用的是Jsdis的hset和hget,存值:Hash的key值、项的key值,数据信息,取值:Hash的key值、项的key值
b、用的Redis哪种存储结构:使用了redis的hset方法,因此底层数据结构是Hash
MyBatis插件
1、一般情况下,开源框架都会i提供插件或其他形式的拓展点,供开发者自行扩展,好处:
a、增加框架灵活性
b、开发者可以结合是继续求对框架进行拓展,使其能够更好的工作
MyBatis插件机智可以实现f嗯也、分表,监控等功能,由于插件和业务无关,业务也无法感知插件,因此可以无感植入插件,在无形中增强功能
2、MyBatis在四大组件处分别提供了简单易用的插件扩展机制(Executor、StatementHandler、ParameterHandler、ResultSetHandler):
a、Executor:执行器,负责增删改查的行为
b、StatementHandler:SQL语法构建器,完成SQL的自编译
c、ParameterHandler:参数处理器,处理参数
d、ResultSetHandler:结果集处理器,处理返回结果集
所谓的MyBatis的插件就是对这四大核心对象进行拦截,就是其拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层动态代理实现的,或者说MyBatis的四大对象都是代理对象
MyBatis所允许拦截的方法如下:
a、Executor: update、query、commit、rollback等
b、StatementHandler:prepare、parametrize、batch、update、query等
c、ParameterHandler:getParameterObject、handleOutParameters等
d、ResultSetHandler:handleResultSets、handleOutParameters等
3、MyBatis插件原理:
在四大对象插件的时候,每个创建出来的对象不是直接返回的,而是通过interceplorChain.pluginAll(paeameterHandler)的方法对创建的对象进行处理
获取到所有的interceptor{拦截器}{产检需要实现的接口}:使用Interceptor.plugin(target)返回target包装后的对象,本质是调用jdk动态代理为当前对象产生一个代理对象
4、第三方插件
a、pageHelper分页插件:将分页的复杂操作进行封装,使用简单的方法即可获得分页的相关数据
导入通用的PageHelper坐标
在mybatis核心配置文件中配置PageHelper插件
b、通用mapper插件:解决单表增删改查重复书写的问题(多个表的单表的增删改查非常相似),开发人员不需要编写sql,不需要在dao中增加方法,只要写好实体类,鞥支持响应的增删改查方法