Mybatis框架
1.什么是框架?
框架是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。
2.使用框架的好处?
框架封装了很多的细节,使开发者可以使用极简的方式实现功能,大大提高开发效率。
3.三层架构
表现层:是用于展示数据的
业务层:处理业务需求
持久层:和数据库交互,将数据持久化
4.持久层技术解决方案
*JDBD技术:
Connection
PrepareStatement
ResultSet
*Spring的JdbcTemplate:
Spring中对jdbc的简单封装
*Apache的DBUtils:
和Spring的JdbcTemplate很像,也是对jdbc的简单封装
*以上这些都不是框架
JDBC是规范
Spring的JdbcTemplate和Apache的DBUtils都只是工具类
因为封装得还不够细致,在开发中还有很多的事务需要我们自己处理
Mybatis入门
1.概述:
*Mybatis是一个用java编写的持久层框架,他封装了jdbc操作的很多细节,是开发者只需要关注sql语句本身,而无需关注注册驱动,创建连接等繁杂的过程,他使用了ORM的思想实现了结果集的封装
*ORM:
Object Relational mapping 对象关系映射
*就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表。
2.mybatis入门:
1.mybatis的环境搭建:
1.创建maven工程并导入坐标
2.创建实体类和dao的接口
3.创建mybatis的主配置文件
SqlMapConfig.xml
4.创建映射配置文件
UserDao.xml
*注意事项:
1.创建UserDao.xml和UserDao.java时名称是为了和之前知识的命名规则一致,在mybatis中他把持久层的操作接口名称和映射文件名称叫做:Mapper
所以:UserDao和UserMapper是一样的,命名不同而已
2.mybatis的映射配置文件的位置必须和dao接口的包结构相同
3.映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
<mapper namespace="com.cc.dao.UserDao">
4.映射配置文件的操作配置(select...),id属性的取值必须是dao接口的方法名
<select id="findAll"> </select>
*当我们遵从了3,,4,5点之后,我们在开发中就无需再写dao的实现类
2.第一个mybattis程序:
1.读取配置文件
2.创建SqlSessionFactory工厂
3.创建SqlSession
4.创建Dao接口的代理对象
5.执行Dao中的方法
6.释放资源
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
UserDao userDao = session.getMapper(UserDao.class);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
注意事项:
在映射配置文件中告知mybatis要封装到哪个实体类中;
配置的方式:指定实体类的全限定类名
resultType="com.cc.domain.User"
3.mybatis基于注解的入门案例:
不需要映射文件,在dao接口的方法上使用注解,如@Select,并且指定sql语句,同时在SqlMapConfig.xml中的mapper配置时,使用class指定dao接口的全限定类名
3.Mybatis的CRUD
*步骤:
1.在dao接口中添加对应的方法
2.在映射配置文件中配置方法对应的sql语句
<!--配置查询所有,id为方法名称,resultType为指定结果集封装的对象-->
<select id="findAll" resultType="User">
select * from user;
</select>
<!--更新用户-->
<update id="updateUser" parameterType="User">
update user set username = #{username},birthday = #{birthday},sex = #{sex},address = #{address} where id=#{id};
</update>
<!--保存用户-->
<insert id="saveUser" parameterType="user">
<!--配置插入操作后,获取插入数据的id-->
<!--其中keyProperty为实体类的属性名,keyColumn为数据库对应的列名,order为指定操作是在插入操作之前还是之后执行-->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select 1697603;
</selectKey>
insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address});
</insert>
<!--删除用户-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{uid};
</delete>
*其中parameterTType属性为输入(参数)类型,resultType属性为返回值(结果集)的类型
3.在方法中使用代理后的userDao对象调用执行语句的方法
//执行查询所有方法
List<User> users = userDao.findAll();
//执行更新操作
userDao.updateUser(user);
//执行保存用户方法
userDao.saveUser(user);
//执行删除操作
userDao.deleteUser(80);
4.解决实体类属性名和数据库列名不对应的两种方式
*不对应会导致查询操作时结果集无法封装到对象中
*解决方式:让属性名和列名匹配
1.在sql语句中起别名:
select id as userId,username as userName,address as userAddress, sex as userSex,birthday as userBirthday from user
*好处:简单有效,执行效率很高
2.在映射配置文件中配置查询结果集的类名和实体类的属性名的对应关系
<!--配置查询结果集的列名和实体类的属性名的对应关系-->
<!--当使用此配置时需要在sql语句配置中将resultType属性替换为resultMap属性,值为resultMap的id值-->
<!--id是唯一标识,type表示的是查询的实体类对应的是哪一个-->
<resultMap id="userMap" type="com.cc.domain.User">
<!--主键字段的配置-->
<!--property指实体类的属性名,严格区分大小写;column指数据库字段名-->
<id property="userId" column="id"></id>
<!--非主键字段的配置-->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>
*缺点:需要多解析一段xml,执行效率相对于第一种方法较低
*好处:提高了开发效率和易于修改
5.typeAlises标签和package标签
1.作用:
1.使用typeAliases配置别名,他只能配置domain中类的别名
*typeAlias用于配置实体类全限定类名的别名,type 属性指定的是实体类全限定类名,alias属性指定别名,当指定了别名之后就不再区分大小写了
*package用于指定要配置的包,当指定后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写
2.mappers标签中的package标签
*package标签是用于指定dao接口所在的包,当指定之后就不会需要再写mapper以及resource或者class了
*注意:如果使用package指定dao接口所在的包的话,则需要映射配置文件的文件名和dao中接口名一致
2.起实体类别名和指定实体类所在的包的用法:
<!--使用typeAliases配置别名,他只能配置domain中类的别名-->
<typeAliases>
<!--typeAlias用于配置别名,type 属性指定的是实体类全限定类名,alias属性指定别名,当指定了别名之后就不再区分大小写了-->
<!--<typeAlias type="com.cc.domain.User" alias="user"></typeAlias>-->
<!--package用于指定要配置的包,当指定后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
<package name="com.cc.domain"/>
</typeAliases>
3.package标签是用于指定dao接口所在的包的用法:
<mappers>
<!--package标签是用于指定dao接口所在的包,当指定之后就不会需要再写mapper以及resource或者class了-->
<package name="com.cc.dao"/>
<!--如果使用package指定dao接口所在的包的话,则需要映射配置文件的文件名和dao中接口名一致-->
</mappers>
Mybatid连接池以及事务控制
1.连接池:
*在开发中多使用连接池的方式,因为他可以减少我们获取连接和释放资源所消耗的时间。
2.mybatis中的连接池:
*mybatis连接池提供了3种方式的配置:
*配置的位置:主配置文件SqlMapConfig.xml中的DataSource标签,type属性就是表示采用何种连接池方式。
*type的取值:
POOLED:采用传统的javax.sql.DataSource接口规范中的连接池,mybatis中有针对规范的实现
UNPOOLED:采用传统的获取连接的方式,虽然也实现了javax.sql.DataSource接口,但是并没有使用池的思想。
JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到的DataSource是不一样的,如果不是web或者maven的war工程,是不能使用的。
*tomcat服务器采用的连接池就是dbcp连接池
*JNDI:
3.mybatis中的事务:
1.数据库事务:
*什么是事务
*事务的四大特性ACID
*不考虑隔离性会产生的3个问题
*解决办法:四种隔离级别
2.mybatis中的事务是通过sqlsession对象的committ方法和roolback方法实现事务的提交和回滚
4.mybatis中的动态sql语句:
<if>标签
<where>标签
<foreach>标签
1.当在开发中遇到需要根据传递的参数来查询的需求时,
如:
/**
* 根据传入参数条件查询
* @return user 查询的条件,有可能对象中的一个属性有值,有可能多个属性有值,也有可能都有值,还有可能都没有
*/
List<User> findUserByCondition(User user);
这时候sql语句的编写就需要动态的添加条件:
*方式:
1.<if>标签拼接sql:
<!--根据条件查询-->
<select id="findUserByCondition" resultType="user" parameterType="user">
select *from user where 1=1
<if test="username!=null">
and username = #{username}
</if>
<if test="sex!=null">
and sex = #{sex}
</if>
</select>
2.使用<where>标签替换1=1:
<select id="findUserByCondition" resultType="user" parameterType="user">
select *from user
<where>
<if test="username!=null">
and username = #{username}
</if>
<if test="sex!=null">
and sex = #{sex}
</if>
</where>
</select>
2.当在开发中遇到需要子查询如:
select * from user where id in(1,2,3,4);
的需求时,使用动态sql传参的方法:
<!--根据QueryVo中的id集合实现查询用户列表-->
<select id="findUserInIds" resultType="user" parameterType="QueryVo">
select *from user
<where>
<if test="ids!=null and ids.size()>0">
<!--colletion表示要遍历的集合,oppen表示要拼接的sql的开始,close表示结束,item表示遍历出来的结果,separator表示遍历出的结果拼接时用什么分割-->
<foreach collection="ids" open="and id in (" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
</where>
</select>
5.mybatis中的多表查询:
1.表之间的关系:
1.一对多
2.多对一
3.一对一
4.多对多
*举例:
一个用户可以有多个订单
多个订单属于同一个用户
用户-->订单:一对多
订单-->用户:多对一
*特例:
每一个订单都只能属于一个用户,
所以mybatis就把多对一看成了一对一。
2.mybatis中的多表查询:
示例:用户和账户:
一个用户可以有多个账户 -->一对多
一个账户只能属于一个用户(多个账户也可以属于同一个用户) -->多对一
步骤:
1.建立两张表:用户表,账户表
*让用户表和账户表之间具备一对多的关系:在账户表中添加外键
2.建立两个实体类
*需要用户和账户的实体类能够体现出一对多的关系
3.建立两个配置文件
*用户的配置文件
*账户的配置文件
4.实现的结果:
*当我们查询用户时,可以同时得到用户下所包含的账户信息 --->一对多关系映射
*实现方式:
1.通过写account子类,在子类中添加对应用户信息的方式。
2.通过mybatis建立实体类关系的方式实现
*在account类中添加主表的对象引用(添加一个User属性),让查询到的结果集全部封装到account中
<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--一对一的关系映射,配置封装user的内容-->
<association property="user" column="uid" javaType="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
</association>
</resultMap>
*当我们查询账户时,可以同时得到账号的所属用户信息 --->多对一关系映射
*实现方式:
通过mybatis建立实体类关系的方式实现
<!--定义封装user和account的resultMap-->
<resultMap id="userAccount" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<!--配置user对象中account集合的映射-->
<!--property:指在类中集合的属性名 ofType:集合中元素的类型-->
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="uid" column="uid"></result>
</collection>
</resultMap>
*示例:用户和角色(身份)
一个用户可以有多个角色
一个角色可以赋予多个用户
步骤:
1.建立两张表:用户表,角色表
*让用户表和角色表具有多对多的关系,需要使用中间表,中间表中包含各表的主键,并关联成外键
2.建立两个实体类:用户实体类和角色实体类
*需要用户和角色的实体类能够体现出多对多的关系
*各自包含一个对方集合引用
3.建立两个配置文件
*用户的配置文件
*账户的配置文件
4.实现的结果:
*当我们查询用户时,可以同时得到用户下所包含的角色信息 --->多对多
*实现方式:
通过mybatis建立实体类关系的方式实现
<resultMap id="userMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
<collection property="roles" ofType="role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
</collection>
</resultMap>
*当我们查询角色时,可以同时得到角色的所赋予的用户信息 --->多对多
*实现方式:
通过mybatis建立实体类关系的方式实现
<resultMap id="roleMap" type="role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
<collection property="users" ofType="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
</collection>
</resultMap>
mybatis中的延迟加载
*在开发中通常会遇到这样一个问题:
在一个用户与账户的关系中,一个用户有100个账户甚至更多
在查询用户的时候,要不要把关联的账户查出来?(非常消耗内存)
在查询账户的时候,要不要把所属的用户查出来?
通常:
在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询出来(延迟加载)
在查询账户时,账户下的用户信息应该是随着账户查询时一起查询出来(立即加载)
*延迟加载
*在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
*立即加载
*不管用不用,只要一调用相关方法,马上发起查询。
*在数据库对应的四种表关系中:一对多,多对一,一对一,多对多
*可以分为:
1.一对多,多对多:通常情况下都是采用延迟加载
2.多对一,一对一:通常情况下都是采用立即加载
*mybatis实现延迟加载:
1.主配置文件中配置延迟加载的全局开关lazyLoadingEnabled,让当前所有的关联对象支持延迟加载
*<settings>
<!--mybatis延迟加载的全局开关-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--配置是立即加载true还是按需加载false,在mybatis3.4.1版本后默认false-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
2.在一对多关系映射的配置中指定查询按需加载内容需要的唯一标识(对方映射文件中条件查询的方法,需要对方的接口支持这个方法)
<!--配置user对象中account集合的映射-->
<!--property:指在类中集合的属性名 ofType:集合中元素的类型-->
<!--select指定的内容是查询账户的唯一标识-->
<!--column指定的内容是指根据唯一标识查询账户时,所需要的唯一标识的值-->
<!--这里的延迟加载相当于先查出一个完整的表,然后再根据表的某一列查询出按需加载的内容-->
<collection property="accounts" column="id" ofType="account" select="com.cc.dao.AccountDao.findAccountByUid"></collection>
mybatis中的缓存
1.什么是缓存:
*存在于内存中的临时数据,
2.为什么使用缓存:
*减少和数据库的交互次数,提高执行效率
3.什么样的数据可以使用缓存,什么样的数据不可以使用缓存
*适用于缓存
1.经常查询并且不经常改变的
2.数据的正确与否对最终结果影响不大的
*不适用于缓存的
1.经常改变的数据
2.数据的正确与否对最终结果影响很大的
*例如:商品的库存,银行的汇率,股市的牌价...
4.mybatis中的一级缓存和二级缓存:
1.一级缓存:
*指的是mybatis中SqlSession对象的缓存;
当我们执行查询后,查询的结果会同时存入SqlSession为我们提供的一块区域中。
该区域的结构是一个Map,当我们再次查询同样的数据,mybatis会先到sqlsession中查询是否有,
有的话直接拿出来用。当SqlSession对象消失时,mybatis的一级缓存也就消失了。
*sqlSession.clearCache();方法可以清除缓存
*一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存
2.二级缓存:
*指的是mybatis中的SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存
*二级缓存的使用步骤:
1.让mybatis支持二级缓存(SqlMapConfig.xml中配置)
*<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2.让当前的映射文件支持二级缓存(在UserDao.xml中配置)
<cache/>
3.让当前的操作支持二级缓存(在select标签中配置)
<select id="findById" parameterType="Integer" resultType="User" useCache="true">
select *from user where id = #{uid};
</select>
*注意:
mybatis的二级缓存中存放的是数据而不是对象,
当使用到缓存中的数据时,他会再创建一个对象,
将缓存中的数据封装到对象中。
mybatis的注解开发:
1.环境搭建:
1.主配置文件的编写
<!--mybatis的主配置文件-->
<configuration>
<!--引入外部资源文件-->
<properties resource="jdbcConfig.properties"></properties>
<!--配置别名-->
<typeAliases>
<package name="com.cc.domain"/>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<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>
</environment>
</environments>
<!--指定带有注解的dao接口所在的位置-->
<mappers>
<package name="com.cc.dao"/>
</mappers>
</configuration>
2.单表CRUD操作
*mybatis中针对CRUD有四个注解:
1.@Select
2.@Update
3.@Insert
4.@Delete
3.多表查询操作
*解决javaBean中属性名和数据库中字段名对不上导致查询到结果无法封装到java对象的问题
*使用@Results注解和@Result注解将属性名和字段名进行映射
第一个属性id为唯一标识,可以被其他方法使用@ResultMap注解引用
第二个属性id表示是否为主键,默认值为false
@Results(id="userMap", value = {
@Result(id = true,property="userId",column = "id"),
@Result(property = "userName",column = "username"),
@Result(property = "userAddress",column = "address"),
@Result(property = "userSex",column = "sex"),
@Result(property = "userBirthday",column = "birthday"),
})
*在@Result注解使用属性one或many表示当前对象和property属性中指定的对象是一对一还是一对多的关系,
*one:一对一,值为@One注解
*many:一对多/多对多,值为@Many注解
*在注解@One和@Many中有属性select和fetchType
*select:值为String,表示查询一对一/多是所依赖的方法所在的位置
*fetchType:值为FetchType.LAZY,FetchType.EAGER,表示延迟加载和立即加载
@Results(id = "accountMap",value = {
@Result(id = true,property = "id",column = "id"),
@Result(property = "uid",column = "uid"),
@Result(property = "money",column = "money"),
@Result(property = "user",column = "uid",//这里的column表示根据什么查询到property中的user
//表示mybatis中的一对一关系映射
one = @One(select = "com.cc.dao.UserDao.findById",//select表示查询结果集使用的方法所在的位置
fetchType = FetchType.EAGER))//fetchType表示是延迟加载(LAZY)还是立即加载(EAGER)
})
4.缓存的配置
*一级缓存:不用开启也不用配置,本来就有的(sqlSession对象的范围)
*二级缓存:mybatis基于注解开发是的二级缓存配置
*步骤:
1.主配置文件配置支持二级缓存
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2.在对应的接口上使用注解@CacheNamespace注解配置属性blocking值为true表示为开启二级缓存(默认值为false)
@CacheNamespace(blocking = true)