框架:是软件开发中一套解决方案,不同的框架解决不同的问题。框架其实就是某种应用的半成品。
软件开发的三层框架:
1.表现层:用于数据展示
2.业务层:处理业务需求
3.持久层:与数据库进行交互
持久层技术解决方案:JDBC技术(是一种规范)、Spring的JDBCTemplate、Apache的DBUtils(是工具类)、Mybatis(框架)。
Mybatis简介
-
Mybatis框架几乎消除了所有JDBC代码,使开发者只需要关注sql语句本身,无需关注注册驱动,创建连接等繁杂的过程。并且Mybatis也基本不需要手工设置参数和获取检索结果。MyBatis能够使用简单的XML文件格式或注解进行配置。
-
Mybatis使用ORM思想,实现了结果集的封装:把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表。
-
Mybatis对于数据库的连接采用托管的方式,对于缓存采用两级缓存,对于结果映射采用自动映射的方式,程序的可维护性高。而JDBC对于数据库的连接是采用编码的方式,而且不支持缓存,对于结果映射采用硬编码,程序的可维护性低
-
每个基于MyBatis的应用都是以一个SqlSessionFactory的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从XML配置文件或一个预先定制的 Configuration的实例构建出 SqlSessionFactory 的实例
Mybatis入门
前期工作:
-
创建maven
1.在maven项目中的xml文件中添加Mybatis的jar包依赖
2.添加JDBC依赖
3.连接数据库 -
创建MyBatis配置
1.在项目中的resource文件夹中添加Mybatis的配置文件(xml)
2.配置文件中需要配置数据库的信息, driver url username password
Mybatis的配置文件mybatis-config.xml:
<?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">
<!--mybatis的主配置文件-->
<configuration>
<!--配置环境-->
<environments default="dev">
<!--mysql环境-->
<environment id="dev">
<!--配置事物类型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<!--配置数据库的四个基本信息-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/memo"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置:每个dao独立的配置文件-->
<mappers>
<mapper resource="com/github/sweeeeeet/dao/IMemoDao.xml"/>
</mappers>
</configuration>
- 编码:
- entity包 实体类–>数据库表(实体类中的属性,与表的列名相同,否则就需要在映射配置文件中,对sql语句起别名,或者用resultMap将查询结果的列名于实体类的属性名建立映射)
- mapper包 mapper接口 -->数据库操作方法
- src/main/resource/mapper 创建mapper.xml定义信息
- 编码,创建SqlSessionFactory接口的实现类SqlSessionFactoryBuilder的对象 (从XML配置文件或一个预先定制的 Configuration的实例构建出 SqlSessionFactory 的实例)
- 测试代码
环境搭建注意事项:
在mybatis中把持久层的操作接口名称和映射文件也叫做dao
在idea中创建resource目录时,他和java包是不一样的,必须分开创建三次才是三级目录结构
mybatis的映射配置文件的位置必须和mapper(dao)接口的包结构相同
映射配置文件的mapper标签的namespace属性的取值必须是dao(mapper)接口的全限定名
映射文件的操作配置select,id属性必须是dao(mapper)接口中的方法名
遵从了这些规定后,我们在开发中就无需再写dao的实现类
mapper.xml配置:
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.github.sweeeeeet.dao.IMemoDao">
<!--配置Dao中的方法(用id标识)查询所有,查询后的结果放在哪里用resultType属性标识:-->
<!--能够执行sql语句,可以获取PreparedStament-->
<select id="findAll" resultType="com.github.sweeeeeet.entity.MemoGroup">
select * from memo_group;
</select>
<!--模糊查询操作-->
<select id="findByName" parameterType="String" resultType="com.github.sweeeeeet.entity.MemoGroup">
<!--#{id}的方式是使用的占位符的方式(推荐),用到了preparedStament-->
<!--SELECT * FROM memo_group WHERE name like #{id};-->
<!--%${value}%的方式是使用的字符串拼接的方式-->
SELECT * FROM memo_group WHERE name like '%${value}%';
</select>
</mapper>
读取配置文件利用了dom4j技术解析xml:
selectList方法:
1.根据配置文件的信息创建Connection对象,注册驱动,获取连接。
2.获取预处理对象PreparedStament,此时需要sql语句
3.执行查询ResultSet set=preparedStatement.excuteQuery();
4.遍历结果集用于封装,通过反射获得实体类对象,把结果封装进实体类:由于实体类的属性和表中的列名是一致的,于是我们就可以把表的列名看成实体类的属性名称,就可以使用反射根据名称获取每个属性,并把值都赋进去。
5.返回list要想让dao中的方法执行,需要提供两个信息:数据库连接信息+映射信息(mapper)。 映射信息包含执行的sql语句和封装结果的实体类的全限定名。
如果有多个dao方法,那么mybatis就会使用map,以key-value的方式存储dao方法和与方法对应的映射,这样多个dao方法就不会混淆。
或是利用java编码构建
try {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(
//通过classLoader传入文件路径,实现一次编写到处运行的理念
Resources.getResourceAsReader("mybatis-config.xml")
);
System.out.println(sqlSessionFactory);
} catch (IOException e) {
e.printStackTrace();
}
DataSource dataSource = new PooledDataSource();//mybatis提供的数据库连接池
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(MemoGroupMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
在test中利用main方法测试是否配置成功:
package com.github.sweeeeeet;
import com.github.sweeeeeet.dao.IMemoDao;
import com.github.sweeeeeet.entity.MemoGroup;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* Author:sweet
* Created:2019/6/30
*/
public class MybatisTest {
/*
* 使用main方法进行测试
* */
public static void main(String[] args) throws IOException {
//1.读取配置文件(使用类加载器的方式)
//使用ServletContext对象的getRealPath()
InputStream in= Resources.getResourceAsStream("mybatis-config.xml");
//2.创建SqlSeonFactory工厂
//利用第三方SqlSessionFactoryBuilder来构建工厂,只需要付钱builder.build(in);即可
//构建者模式能把对象创建的细节隐藏,使用者直接调用方法即可调用对象
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
SqlSessionFactory factory= builder.build(in);
//3.使用工厂生产SqlSession对象
//降低类之间的依赖关系,进行解耦,不用在每次构建的时候都new对象
SqlSession session=factory.openSession();
//4.使用SqlSession创建DAO接口的代理对象
//创建dao接口实现类使用了代理模式,就能够在不修改源码的基础上对已有的方法增强
IMemoDao memoDao=session.getMapper(IMemoDao.class);
//5.使用代理对象的执行方法
List<MemoGroup> memoGroups=memoDao.findAll();
for(MemoGroup m:memoGroups){
System.out.println(m);
}
//6.释放资源
session.close();
in.close();
}
}
使用SqlSession创建DAO接口的代理对象:
IMemoDao memoDao=session.getMapper(IMemoDao.class);
getMapper内部会实现一个类加载器,他使用和被代理对象实现相同的接口
代理的过程:他是一个增强方法,我们需要自己提供一个接口的实现类,在实现类中调用selectList()方法
用annotation注解的方式配置dao映射
1.删除resource目录下的映射配置结构
2.在dao包中对dao接口添加注解
public interface MemoGroupMapper {
@Select("insert into")
int insertMemoGroup(MemoGroup memoGroup);
}
3.更改mybatis配置文件中的mapper映射:使用class属性指定dao接口的全限定名
<!--指定映射配置文件的位置:每个dao独立的配置文件-->
<mappers>
<mapper class="com.github.sweeeeeet.mapper.MemoGroupMapper"/>
</mappers>
不论是xml方式配置映射还是注解方式配置映射,都不需要程序员自己写实现类,mybatis内部会自动创建dao接口的实现类,然后调用实现类中的方法进行dao操作。
Mybatis的配置
1.properties
配置properties可以在标签内部property中配置连接数据库的信息,也可以通过属性引用外部外部配置文件信息。
- resource属性用于指定配置文件的位置,必须存在与类路径下
- url属性:要求按照url的写法书写:由协议、主机、端口、uri组成
- uri:统一资源标示符,在应用中可以唯一的定位一个资源
1.在 properties 元素体内指定的属性⾸先被读取。
2.然后根据 properties 元素中的 resource 属性读取类路径下属性⽂件或根据 url 属性指定的路径读取属性⽂件,并覆盖已读取的同名属性。
3.最后读取作为⽅法参数传递的属性,并覆盖已读取的同名属性。
因此,通过⽅法参数传递的属性具有最⾼优先级,resource/url 属性中指定的配置⽂件次之,最低优先级的是 properties 属性中指定的属性。
2.typeAliases
typeAliase用于配置别名
- type属性用于指定实体类的权限定名
- alias属性指定别名,当指定了别名就不再区分大小写
package用于指定要配置的包
- 当指定之后,该包下的实体类就会注册别名,并且类名就是别名,不再区分大小写
Mybatis的编码映射
1. Select 命令
#{id}进行字符串匹配的方式是使用的占位符的方式(推荐),用到了preparedStament
%${value}%的方式是使用的字符串拼接的方式
<!--模糊查询操作-->
<select id="findByName" parameterType="String" resultType="com.github.sweeeeeet.entity.MemoGroup">
<!--SELECT * FROM memo_group WHERE name like #{id};-->
SELECT * FROM memo_group WHERE name like '%${value}%';
</select>
2. insert, update和delete
<!--增-->
<insert id="save" parameterType="com.github.sweeeeeet.entity.MemoGroup">
insert into memo_group(id,name,created_time,modify_time)values(#{id},#{name},#{create_time},#{modify_time});
</insert>
<!--删-->
<delete id="delete" parameterType="Integer">
delete from memo_group where id=#{id};
</delete>
<!--改-->
<update id="update" parameterType="com.github.sweeeeeet.entity.MemoGroup">
update memo_group set name=#{name},created_time=#{create_time} where id=#{id};
</update>
对于需要新增用户ID的返回值的情况:
<!--配置插入数据后,需要获得插入数据id-->
<insert id="save" parameterType="com.github.sweeeeeet.entity.MemoGroup">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
SELECT last_insert_id();
</selectKey>
insert into memo_group(id,name,created_time,modify_time)values(#{id},#{name},#{create_time},#{modify_time});
</insert>
Insert,Update,Delete的配置属性信息:
3. resultMap
resultMap配置查询结果和列名的对应关系:
- id是这个对应关系的唯一标识,
- type是对应的实体类
id标签对应主键与实体类的对应
resultMap标签是非主键字段的对应
<!--配置查询结果和列名的对应关系:id是这个对应关系的唯一标识,type是对应的实体类-->
<resultMap id="userMap" type="com.github.sweeeeeet.entity.MemoGroup">
<!--主键的对应:property是实体类中的属性,column是表中的字段-->
<id property="userid" column="id"></id>
<!--非主键的对应-->
<result property="username" column="name"></result>
</resultMap>
用了resultMap结果映射的mapper,在配置dao接口方法的映射时,需使用resultMap=id来指定返回结果
<select id="count" resultMap="userMap">
SELECT count(*) from memo_group ;
</select>
4.package
用于指定dao接口所在的包,当指定后就不需要再写mapper以及resource或class了
Mybatis连接池
连接池:在实际开发中,连接池可以减少我们获取连接所消耗的时间。连接池就是用于存储连接的一个容器,容器就是一个集合对象,该集合必须是具有队列的特性:先进先出,且是线程安全的,不能两个线程拿到同一个连接。
mybatis 提供的三种方式的配置:
配置的位置:主配置文件SqlMapConfig.xml中的dataSource标签
连接池的分类
type属性表示采用何种连接池方式。type属性的取值:
- POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现:是从池中获取一个连接来用
内部分为空闲连接池和活动池,如果空闲连接池还有连接的话,直接拿出一个来用。如果空闲连接池中的连接已经没有了,就会判断活动连接池中是否已经达到了最大数量,如果已经到达最大数量,就会接着判断活动池中哪个是最先进来的,将它返回回去。
- UNPOOLED:采用传统的获取连接的方式,虽然也实现javax.sql.DataSource接口,但是并没有使用池的思想:每次创建一个新连接使用
- JNDI:采用服务器提供的JNDI技术实现,获取DataSource对象,不同的服务器所能拿到的DataSource是不一样的。注:如果不是web或maven的war工程。则是不能使用的。在tomcat服务器中,采用的连接池是dbcp连接池。
Mybatis中的事务
Mybatis中的事务与JDBC中的事务提交过程一致,都需要创建事务,最后进行commit提交。
//使用工厂生产SqlSession对象,并自动提交事务
session = factory.openSession(true);
(面试中常遇到的关于事物的问题)
- 事物的四大特性:ACID
A:原子性Atomicity:表示事务是不可分割的最小单位,原子性保证事务要么全起作用,要么全不起作用
C:一致性consistency:执行事务前后数据保持一致,多个事务读到同一个数据内容是一致的
I:隔离性Isolation:进行并发访问数据库时,一个事务不会被其他事务所干扰,与其他事务是隔离的。
D:持续性Durability:事务对数据库的改变是持久的。
什么是事物?
事务是逻辑上的一组操作,要么全执行,要么全不执行。
不考虑隔离性会产生的三个问题?
脏读:当一个事务正在修改数据库,还没有将数据提交到数据库,另一个事务就对数据库进行了访问,那么另一个事务拿到的数据就称为脏数据。进行操作得到的结果也是不正确的。
幻读:当一个事务进行插入删除操作时,在另一个事务内看到数据个数不一样的情况。例如一个事务读取了几行数据,另一个事务就进行了插入删除操作,那么第一个事务就会发现多了或少了一些原本不存在的数据,就像发生了幻觉一样。
不可重复读:由于事务的修改,另一个事务两次读到的数据是不同的。
解决办法:四种隔离级别
读未提交,读已提交,可重复读,可串行化。
Mybatis动态SQL语句
dao映射配置文件中的标签
1.if
对于一些查询的sql语句,对于查询的子条件并不确定是否存在,因此可以采用动态SQL语句根据传入的参数条件查询。
<if test="逻辑条件">
条件表达式;
</if>
where is_protected = '1'
<if test="title!=null">
and title like #{title}
</if>
2.where标签
可以对if标签的条件进行判断,当有一个以上的if标签逻辑表达式为true,才去插入where子句。⽽且,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除。
<select id="queryMemoInfoWithPrivacyAndLikeTitle" resultMap="memoInfoMap">
select * from memo_info
<where>
<if test="title!=null">
and title like #{title}
</if>
</where>
</select>
3.set元素
set 元素可以被⽤于动态包含需要更新的列,⽽舍去其他的逗号等内容。⽐如:
<update id="updateMemoInfo">
update memo_info
<set><if test="title!=null">
title = #{title},
</if> <if test="content!=null">
content = #{content},
</if>
</set>
where id = #{id}
</update>
4.choose元素
有些时候,我们不想⽤到所有的条件语句,⽽只想从中择其⼀⼆。mybatis提供了choose元素,类似于Java中的switch语句,在mybatis中利用choose、when、otherwise的结构来表达
<select id="queryMemoInfoWithPrivacyAndLikeTitleOrBackground"
resultMap="memoInfoMap">
select * from memo_info
where is_protected = '1'
<choose>
<when test="title!=null">
and title like #{title}
</when>
<when test="background!=null">
and background = #{background}
</when>
<otherwise>
and background = 'WHITE'
</otherwise>
</choose>
</select>
5.foreach
当构建in条件语句时,需要遍历集合中的元素,需要用到foreach.
!-- 映射接⼝ -->
List<MemoInfo> queryMemoInfoWithIdsList(List<Integer> ids);
<!-- 命令配置 -->
<select id="queryMemoInfoWithIdsList" parameterType="list"
resultMap="memoInfoMap">
SELECT * FROM memo_info
WHERE id in
<foreach item="item" index="index" collection="list" open="("
separator="," close=")">
#{item}
</foreach>
</select>
foreach 元素的功能是⾮常强⼤的,它允许你指定⼀个集合,声明可以⽤在元素体内的集合项和索引变量。它也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。
foreach 可以将任何可迭代对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数。
当使⽤可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。
当使⽤字典(或者Map.Entry对象的集合)时,index是键,item是值。
6.抽取重复的sql语句–sql标签
<sql id="defaultUse">
select * from user
</sql>
表之间的关系
1.一对多
2.多对一
3.一对一
4.多对多
Mybatis中的多表操作(重要)
用户和账户
一个用户可以有多个账户,一个账户只能属于一个用户(多个账户也可以属于同一个用户)。
建立多表查询:
- 建立两张表:用户类、账户类
- 建立两个实体类:用户实体类和账户实体类
- 建立两个配置文件:用户的配置文件和账户的配置文件
- 实现配置:当我们查询用户时,可以同时得到用户下所包含的账户信息,当我们查询账户的时候,可以同时得到账户的所属的用户信息。
Mybatis中的延迟加载
在真正使用数据时才发起查询,不用的时候不查询
立即加载:不管用不用,只要一调用方法就发起查询。
在对应的四种关系中:
一对多,多对多:通常情况下采用延迟加载
多对一,一对一:通常情况下采用立即加载
缓存:存在于内存中临时数据,使用缓存可以减少和数据库的交互次数,提高执行缓存效率。
适用于缓存的场景:
- 经常查询且不常改变的数据。
- 数据的正确与否对最终的结果影响不大
不适用于缓存的场景:商品的库存、银行的汇率、股市的涨价
一级缓存:
Mybatis中SqlSession对象的缓存,当我们执行查询后,查询的结果会同时存入到SqlSession为我们提供一块区域中,该区域的结果是一个Map.当我们再次查询同样的数据,mybatis会先去sqlSession中查询是否有,有的话直接拿来用。当sqlSession对象消失时,mybatis的一级缓存也就消失了。当调用sqlSession的修稿commit(),close()等方法时,就会清空一级缓存。
二级缓存
Mybatis中SqlSessionFactory的对象缓存,由同一个SqlSessionFactory对象创建的SqlSessionFactory共享其缓存。