Mybatis框架
Mybatis入门
什么是框架?
1.什么是框架?
- 它是我们软件开发中一套解决方案,不同的框架解决的是不同的问题
2.使用框架的好处
- 框架封装了很多细节,使开发者可以使用极简的方式实现功能。大大提升开发效率
3.三层架构
-
表现层
- 是用于展示数据的
-
业务层
- 是处理业务需求的
-
持久层
- 是和数据库交互的
4.持久层的技术解决方案
-
JDBC技术:
- Connection
- PreparedStatement
- ResultSet
-
Spring的JDBCTemplate:
- Spring中对JDBC的简单封装
-
Apache的DBUtils:
- 它和spring的JDBCTemplate很像,也是对JDBC的简单封装
-
以上的都不是框架
- JDBC是规范
- Spring的JDBCTemplate和Apache的DBUtils都只是工具类
5.mybatis框架概述
- 是一个优秀的java持久层框架,它内部封装了JDBC,是我们只需要关注sql语句本身,而不需要去加载驱动,建立连接,创建statement等繁杂的过程。
- mybatis是通过xml或者注解来进行配置
- 它使用了ORM思想实现了结果集的封装
- ORM:
- Object Relational Mappging 对象关系映射
- 简单的说就是把数据库表和实体类的属性对应起来,让我们可以操作实体类就可以实现操作数据库表
mybatis入门
mybatis环境搭建
-
坐标引入
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency>
-
环境搭建
<?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="development">
<!--配置development的环境-->
<environment id="development">
<!--配置事务的类型-->
<transactionManager type="JDBC"/>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<!--配置连接数据库的四个基本要素-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<mapper resource="dao/IUserDao.xml"/>
</mappers>
</configuration>
- 查询所有接口
package dao;
import domain.User;
import java.util.List;
/**
* 用户持久层接口
*/
public interface IUserDao {
/**
* 查询所有
* @return
*/
public List<User> findAll();
}
- 创建映射配置文件,针对上接口的
- resultType里写返回的类型(我这里写的是自己定义的类,这里如果返回int直接写int就行)
- 这里因为是查询所有,所以使用的是select标签,如果是其他的就用其他的标签(例如删除就用 Delete标签)
- 标签内写sql语句
<?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="dao.IUserDao">
<!--配置查询所有-->
<select id="findAll" resultType="domain.User">
select * from user;
</select>
</mapper>
注意事项
-
创建IUserDao.xml和IUserDao.java是名称是为了和之前学的保持一致
在mybatis中它把持久层的操作接口名称和映射文件也叫作:Mapper
所以:IUserDao和IUserMapper是一样的
-
在idea中创建目录的时候跟包是不一样的
- 包在创建:com.heima.dao它是三级结构
- 而目录在创建时:com.heima.dao是一级目录
-
mybatis的映射配置文件位置必须和dao接口包结构相同
-
namespace哪里写的是到这个xml的全限定类名
-
id里要写接口里的方法名
- 当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类
mybatis入门案例
第一步:读取配置文件(SqlMapCofig.xml)
括号里绝对路径和相对路线的缺点:
绝对路径:比如说(d:/xxx/xxx.xml) 如果我的D盘没有怎么办?
相对路径:(src/java/main/xxx.xml) 如果是web工程一部署src就没了怎么办?
所以这两种方法在实际开发过程中用的几率都不大
我们读配置文件只有两种:
第一:使用类加载器,但是它只能读取类路径的配置文件
第二:使用ServletConntext对象的getRealPath()
这里是为了读取以下的数据
第二步:创建一个SqlSessionFactory工厂
mybatis把工厂的创建细节省略了,它提供了SqlSessionFactoryBuilder对象,这个对象是可以直接创建的
创建这个对象的好处就是我们用builder.build(in)吧inputstream这个流传进去
这里就是框架的好处,它把细节都封装好了,让我们用极简的方式来实现功能
创建工厂mybatis使用了构建者模式(构建者模式简单来说就是我们找了一个专业团队来按照我们需求来建造工厂,我们只需要给钱,这里in就是钱,builder就是构建者)
优势:把对象的创建细节隐藏,使使用者直接调用方法即可拿到对象
第三步:使用工厂生产一个SqlSession对象
生产SqlSession使用了工厂模式
优势:解耦(降低类之间的依赖关系)
第四步:使用SqlSession创建Dao接口的代理对象
括号里写dao字节码
创建Dao接口实现类使用了代理模式
优势:不修改源码的基础上对已有的方法增强
第五步:使用代理对象执行方法
第六步:释放资源
注意事项:千万不要忘记在映射配置中告知mybatis要封装到那个实体类中(就是dao的xml中的resultType)
配置方式:指定实体类的全限定类名
自定义mybatis的分析
mybatis在使用代理dao的方式实现增删改查时做什么事?
第一:创建代理对象
第二:在代理对象中调用selectList
分析
我们有什么?
这是连接数据库的信息,有了它们就能创建connection对象
有了它就有了映射配置信息
有了它就有了执行的SQL语句,就可以获取PreparedSatement
此配置中还有封装的实体类全限定类名
读取配置文件
用到的技术就是解析XML的技术
此处用的是dom4j解析xml技术
mybatis的执行过程
这里是selectList的执行过程
要让上方的方法执行,我们需要给方法提供两个信息
第一:连接信息
第二:映射信息
- 映射信息包含了两个部分:
- 第一:执行的SQL语句
- 第二:封装结果的实体类全限定类名
- 把这两个部分组合起来定义成一个对象
- 第一:执行的SQL语句
既然定义一个对象我们改怎么确认那个是那个呢?
将这个对象存入map里
mybatis的CRUD
select
<select id="findAll" resultType="doMain.User">
select * from user;
</select>
//读取配置文件,生成字节输入流
in= Resources.getResourceAsStream("SqlMapConfig.xml");
//获取SqlSessionFactory
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(in);
//获取SQlSession对象
sqlSession=factory.openSession();
//获取dao的代理对象
userDao=sqlSession.getMapper(IUserDao.class);
//执行查询所有方法
List<User> users=userDao.findAll();
for (User user : users) {
System.out.println(user);
}
//释放资源
sqlSession.close();
in.close();
insert
<insert id="saveUser" parameterType="doMain.User">
insert into user(username,address,sex,birthday)value(#{username},#{address},#{sex},#{birthday});
</insert>
User user =new User();
user.setUsername("mybatis saveUser");
user.setAddress("江西省南昌市");
user.setSex("男");
user.setBirthday(new Date());
//读取配置文件,生成字节输入流
in= Resources.getResourceAsStream("SqlMapConfig.xml");
//获取SqlSessionFactory
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(in);
//获取SQlSession对象
sqlSession=factory.openSession();
//获取dao的代理对象
userDao=sqlSession.getMapper(IUserDao.class);
//执行增加方法
userDao.saveUser(user);
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
in.close();
update
<update id="updateUser" parameterType="doMain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
</update>
User user =new User();
user.setId(50);
user.setUsername("刘某人");
user.setAddress("江西省南昌市");
user.setSex("女");
user.setBirthday(new Date());
//读取配置文件,生成字节输入流
in= Resources.getResourceAsStream("SqlMapConfig.xml");
//获取SqlSessionFactory
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(in);
//获取SQlSession对象
sqlSession=factory.openSession();
//获取dao的代理对象
userDao=sqlSession.getMapper(IUserDao.class);
//执行修改方法
userDao.updateUser(user);
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
in.close();
Delete
<delete id="deleteUser" parameterType="int">
delete from user where id=#{uid}
</delete>
//读取配置文件,生成字节输入流
in= Resources.getResourceAsStream("SqlMapConfig.xml");
//获取SqlSessionFactory
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(in);
//获取SQlSession对象
sqlSession=factory.openSession();
//获取dao的代理对象
userDao=sqlSession.getMapper(IUserDao.class);
//执行删除方法
userDao.deleteUser(43);
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
in.close();
findById
<select id="findById" resultType="doMain.User" parameterType="int">
select * from user where id=#{uid};
</select>
//读取配置文件,生成字节输入流
in= Resources.getResourceAsStream("SqlMapConfig.xml");
//获取SqlSessionFactory
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(in);
//获取SQlSession对象
sqlSession=factory.openSession();
//获取dao的代理对象
userDao=sqlSession.getMapper(IUserDao.class);
//执行根据id查询方法
User user = userDao.findById(50);
System.out.println(user);
//提交事务
sqlSession.commit();
//释放资源
sqlSession.close();
in.close();
findByName
<select id="findByName" resultType="doMain.User" parameterType="string">
select * from user where username like #{name};
</select>
//执行模糊查询方法
List<User> users = userDao.findByName("%王%");
for (User user : users) {
System.out.println(user);
}
findTotal
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
//查询总记录数
int count = userDao.findTotal();
System.out.println(count);
mybatis中参数的深入
parameterType(输入类型)
-
传递简单类型
- 如(int,string)
-
传递pojo对象
- Mybatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称
- ognl表达式
- Mybatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称
-
传递pojo包装对象
-
开发中通过pojo传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件还包括其他的查询条件(比如将用户购买商品信息也做为查询条件),这时可以使用包装对象传递输入参数。Pojo类中包含pojo。
-
例如(根据用户名查询用户信息,查询条件放到QueryVo的user 属性中)
-
public class QueryVo { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
-
<select id="findByVo" resultType="doMain.User" parameterType="doMain.QueryVo"> select * from user where username like #{user.username}; </select>
-
QueryVo vo=new QueryVo(); User user=new User(); user.setUsername("%王%"); vo.setUser(user); //执行模糊查询方法 List<User> users = userDao.findByVo(vo); for (User u : users) { System.out.println(u); }
-
resultType(输出类型)
- 传递简单类型
- 如(int,string)
- 输出pojo对象
- 输出pojo列表
resultMap
- id属性可以随便写
- type属性查询的实体类
- id标签
- property属性填查询实体类的对应数据库的主键
- colum属性填数据库的主键
- result标签
- colum属性填数据库的列名
- property属性填对应的名字
- association标签
- colum属性指定的内容是用户根据ID查询时,所需要的参数的值
- property属性填外键的类
- javaType属性填要封装到那个类
- select属性指定内容是查询用户的唯一标志
Mybatis深入
Mybatis中连接池以及事务控制
连接池
- 我们在实际开发中都会使用连接池。因为它可以减少我们获取连接所消耗的时间。
- 连接池就是一个用于存储连接的容器
- 容器就是一个集合,该集合必须是线程安全,不能两个线程拿到同一个连接
- 该集合还必须实现队列的特性:先进先出
- 当拿走连接池中的一个连接后,后面的连接会从新排序,而使用后归还的连接会排在最后面
Mybatis中的连接池
- Mybatis连接池提供了3种方式的配置
- 配置的位置:
- 主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示采用何种连接池方式。
- type属性的取值:
- POOLED
- POOLED采用的是传统的javax.sql.DataSource规范中的连接池,mybatis有针对规范的实现
- POOLED是从池中获取一个连接来用
- UNPOOLED
- UNPOOLED采用的是传统的获取连接的方式,虽然也实现了javax.sql.DataSource接口,但并没有使用连接池的思想
- UNPOOLED是每次创建一个连接来用
- JNDI
- JNDI采用的是服务器提供的JNDI技术实现,来采取DataSource对象,不同的服务器所能拿到的DataSource是不一样的
- 注意:如果不是web或者Maven的war工程,是不能使用的
- tomcat服务器,采用连接池就是dbcp连接池。
- POOLED
- 配置的位置:
事务
- 什么是事务
- 事务是一组原子操作单元,从数据库角度说,就是一组SQL指令,要么全部执行成功,若因为某个原因其中一条指令执行有错误,则撤销先前执行过的所有指令。更简答的说就是:要么全部执行成功,要么撤销不执行。
- 事务的四大特性
- 事务的原子性:表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。
- 事务的一致性:表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。
- 事务的隔离性:表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。
- 事务的持久性:表示已提交的数据在事务执行失败时,数据的状态都应该正确。
- 事务的隔离性
- 所谓事务的隔离性,其实事务的这个属性是针对数据库访问的并发性问题而言的。
- 不考虑事务隔离性会出现的三大问题
- 脏读
- 一个事务处理过程里读取了另一个未提交的事务中的数据。
- 幻读也叫虚读
- 一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。幻读是事务非独立执行时发生的一种现象。
- 不可重复读
- 一个事务两次读取同一行的数据,结果得到不同状态的结果,中间正好另一个事务更新了该数据,两次结果相异,不可被信任。
- 注意事项
- 不可重复读和脏读的区别:脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
- 幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
- 脏读
- 隔离级别
- Read uncommitted(未授权读取、读未提交)
- 如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。这样就避免了更新丢失,却可能出现脏读。也就是说事务B读取到了事务A未提交的数据。
- Read committed(授权读取、读提交)
- 读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。该隔离级别避免了脏读,但是却可能出现不可重复读。事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
- Repeatable read(可重复读取)
- 可重复读是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,即使第二个事务对数据进行修改,第一个事务两次读到的的数据是一样的。这样就发生了在一个事务内两次读到的数据是一样的,因此称为是可重复读。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。这样避免了不可重复读取和脏读,但是有时可能出现幻象读。(读取数据的事务)这可以通过“共享读锁”和“排他写锁”实现。
- Serializable(序列化)
- 提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。
- 注意事项
- 大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。
Mysql的默认隔离级别是Repeatable read。 - 隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC操作数据库来说,一个Connection对象相当于一个链接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其他链接Connection对象无关。
- 设置数据库的隔离级别一定要是在开启事务之前。
- 大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。
- Read uncommitted(未授权读取、读未提交)
mybatis中的事务
- mybatis中的事务它是通过Sqlsession对象的commit和rollback方法实现事务的提交和回滚
动态sql
if标签
如果在test里要写两个条件那两个条件中间要写and而不是&&
where标签
where标签是使sql语句后不需要加where 1=1
forecah标签
- collection属性填要循环的集合
- open属性填在select * from user后面加的语句括号加半边就可以了
- close属性加另外半边括号
- item属性填集合中取出值的别名
- separator属性填用什么分隔开
sql标签
-
sql标签主要就是抽取重复语句比如图片那句
-
id属性随便填,后面使用这个语句时在refid中填就好了
-
include标签是配合sql标签使用的
-
如果后面要拼接sql语句,注意不要在语句后加;号
mybatis中的多表查询
表之间的关系有四种:
- 一对多
- 多对一(Mybatis把多对一看成一对一)
- 一对一
- 多对多
一对一
<select id="findAll" resultMap="AccountUserMap">
select u.*,a.id as aid,a.uid,a.MONEY from account a,user u where u.id=a.uid;
</select>
<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 column="username" property="username"></result>
<result column="address" property="address"></result>
<result column="sex" property="sex"></result>
<result column="birthday" property="birthday"></result>
</association>
</resultMap>
public void findAll(){
List<Account> accounts=accountDao.findAll();
for (Account account : accounts) {
System.out.println(account);
System.out.println(account.getUser());
}
}
一对多
<!--查询所有-->
<select id="findAll" resultMap="userAccountMap">
select * from user u left outer join account a on u.id = a.UID;
</select>
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sesx"></result>
<result property="birthday" column="birthday"></result>
<collection property="accounts" ofType="account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
public void findAll(){
List<User> users=userDao.findAll();
for (User user : users) {
System.out.println("--------------------------");
System.out.println(user);
System.out.println(user.getAccounts());
}
}
多对多
<!--查询所有-->
<select id="findAll" resultMap="userAccountMap">
select u.*,r.id as rid,r.ROLE_NAME,r.ROLE_DESC from user u
left outer join user_role ur on u.ID = ur.uID
left outer join role r on r.id = ur.RID
</select>
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sesx"></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>
public void findAll(){
List<User> users=userDao.findAll();
for (User user : users) {
System.out.println("--------------------------");
System.out.println(user);
System.out.println(user.getRoles());
}
}
这里是根据用户来查询职业
<select id="findAll" resultMap="roleMap">
select u.*,r.id as rid,r.ROLE_NAME,r.ROLE_DESC from role r
left outer join user_role ur on r.ID = ur.RID
left outer join user u on ur.UID = u.id
</select>
<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>
public void findAll(){
List<Role> roles=roleDao.findAll();
for (Role role : roles) {
System.out.println("--------------------------");
System.out.println(role);
System.out.println(role.getUsers());
}
}
这里是根据职业来查询用户
mybatis中的延迟加载
延迟加载
- 在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
立即加载
- 不管用不用,只要一调用方法,马上发起查询。
对应的四种表关系
一对多,多对多:通常情况下我们都是采用延迟加载。
多对一,一对一:通常情况下我们都是采用立即加载。
mybatis中的缓存
什么是缓存呢?
- 存在于内存中的临时数据
为什么使用缓存
- 减少和数据库的交互次数,提高执行效率
适用于缓存的条件
- 经常使用并且不经常改变的
- 数据的正确与否对最终结果影响不大的
不适用于缓存的
- 经常改变的数据
- 数据的正确与否对最终结果影响很大的
- 例如:商品的库存,银行的汇率,股市的牌价
一级缓存
- 它指的是Mybatis中SqlSession对象的缓存
- 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中。
- 该区域的结果是一个Map。当我们再次查询同样的数据,mybatis会先去SqlSession中查询是否有,有的话直接拿出来
- 当SqlSession对象消失时,mybatis的一级缓存也就消失了
二级缓存
- 它指的是mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存
- 二级缓存的使用步骤:
- 第一步:让mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
- 第二步:让当前的映射文件支持二级缓存(在IUserDao中配置)
- 第三步:让当前的操作支持二级缓存(在select标签中配置)
注解
- 在mybatis中针对CRUD一共有四个注解
- @select
- @insert
- @update
- @delete
select
@Select("select * from user")
List<User> findAll();
在接口方法上写@Select
其他都一样