概述
可重用设计,软件开发一套解方案,封装了很多细节
Mybatis封装了jdbc,使开发者只需要关注SQLyujv本身。
使用ORM(Object Relational Mapping 对象关系映射)思想,简单来说就是将数据库的数据表及其中的列名和实体类及实体类的属性对应起来。这样,我们就能通过操作实体类来操作数据表。(实体类的属性名必须和数据表的列名一致)
三层架构
1、数据访问层(持久层):主要是对非原始数据(数据库或者文本文件等存放数据的形式)的操作层,而不是指原始数据,也就是说,是对数据库的操作,而不是数据,具体为业务逻辑层或表示层提供数据服务。
(Mybatic)
2、业务逻辑层(业务层):主要是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理,如果说数据层是积木,那逻辑层就是对这些积木的搭建。
(spring)
3、界面层(表现层):主要表示WEB方式,也可以表示成WINFORM方式,WEB方式也可以表现成:aspx,如果逻辑层相当强大和完善,无论表现层如何定义和更改,逻辑层都能完善地提供服务。
(springmvc (MVC: model view controller)模块 视图 控制)
关于事务泄露
入门案例
配置mybatis
-
添加依赖
-
在resources中添加配置文件SqlMapConfig(名字随意,一般为这个)。设置约束、配置环境、指定映射配置文件(每个dao独立的配置文件)的路径。
-
创建映射配置文件,文件位置与dao文件需要对应。
-
映射配置文件mapper的标签namespace属性的取值必须为dao接口的全限定名
-
映射配置文件的操作配置(select)中的id属性取值必须为dao接口中对应的方法名。
测试
//1.获取配置参数
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.通过工厂对象创建SqlSession对象
SqlSession sqlSession = factory.openSession();
//4.使用SqlSession对象创建dao代理对象
IUserDao dao = sqlSession.getMapper(IUserDao.class);
//5.使用dao代理对象执行方法
List<User> users = dao.findAll();
//6.释放资源
sqlSession.close();
in.close();
自定义Mybatis框架
mabitis在使用dao方式实现查询时做什me事呢?
- 创建代理对
- 在代理对象中调用selectList
- 简单的查询需要如下信息,将他们封装成一个mapper。
- 因为每一个方法都有一个mapper。为了区分将所有mapper放入一个map集合中。
- 如何创建代理对象
创建所需类
依次创建
删除mabatis依赖后配置文件中会出现如下错误,将报错部分约束删除即可
解析xml的工具类
需要Configuration类
通过源码可知创建该类需要包含参数driver、url、username、password、mappers
需要注意的是在构造器中给mappers赋值采用的是putall方法,此时map对象必须new出来,否则会报错
mapper类需要创建用于存储数据库语句sqlString和返回结果的实体类resultType
回到SqlSessionFactoryBuilder编写build方法
public SqlSessionFactory build(InputStream config){
Configuration configuration = XMLConfigBuilder.loadConfiguration(config);
return new DefaultSqlSessionFactory(configuration);
}
创建DefaultSqlSessionFactory类实SqlSessionFactory接口
public class DefaultSqlSessionFactory implements SqlSessionFactory {
//工厂需要操作数据库,需要有数据库的连接等信息
private Configuration cfg;
public DefaultSqlSessionFactory(Configuration cfg) {
this.cfg = cfg;
}
public SqlSession openSession() {
return new DefaultSqlSession();
}
}
创建DefaultSqlSession类实SqlSession接口用于创建代理对象
public class DefaultSqlSession implements SqlSession {
/**
* 获取代理对象
* @param daoInterfaceClass dao的接口字节码
* @param <T>
* @return
*/
public <T> T getMapper(Class<T> daoInterfaceClass) {
return null;
}
/**
* 关闭资源
*/
public void close() {
}
}
编写getmapper方法创建代理对象
通过Proxy创建方法如下
Proxy.newProxyInstance(被代理对象的类加载器, 被代理对象实现的接口, 如何实现)
Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},
new MapperProxy(cfg.getMappers(), DataSourceUtil.getConnection(cfg)));
在如何实现处,需要创建一个实体类MapperProxy实现InvocationHandle接口用于编写代理对象的增强方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取方法名
String name = method.getName();
//获取方法所在类的名称
String className = method.getDeclaringClass().getName();
//组合key值
String key = className + "." + name;
//通过key值获取mapper
Mapper mapper = map.get(key);
if(mapper == null){
throw new IllegalArgumentException("传入的参数有误");
}
//调用工具类查询所有
return new Executor().selectList(mapper,conn);
}
反向生成工具
官网http://mybatis.org/generator/
创建配置文件generatorConfig.xml
添加依赖
在pom中添加插件
忽略注释
修改配置文件
运行
使用注解方式
1.修改配置文件SqlMapConfig.xml
2.在接口中的方法上添加select注解
3.创建注解类
注解方式解析:
mybatis中的crud(通过代理方式)
- 增加用户
IUserDao.xml配置
<!--保存用户 #{}为mybatis通过反射获取参数,内容为User中get后的名字-->
<insert id="saveUser" parameterType="com.jc.domain.User">
insert into user (username,address,sex,birthday)
values(#{username},#{address},#{sex},#{birthday});
</insert>
注意保存删除等操作需要手动提交事务
- 更新和删除
删除
更新
<!--修改用户-->
<update id="updateUser" parameterType="com.jc.domain.User">
update user set username=#{username},sex=#{sex},birthday=#{birthday},address=#{address} where id=#{id};
</update>
- 模糊查询
在插入数据时,如何获取自增属性的值,如user的id
在配置插入操作时添加如下代码
或者 mysql可以如下:
使用实体类的包装对象作为查询条件
1.创建QueryVo类将实体类User封装
public class QueryVo {
private User user;
public QueryVo(User user) {
this.user = user;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
2.配置xml
3.测试
@org.junit.Test
public void findByVo(){
User u = new User();
u.setName("%王%");
QueryVo vo = new QueryVo(u);
System.out.println(userDao.findByVo(vo));
}
当实体类属性名和数据库列名不一致时,如何应对
方法1、使用as作为列名的别名。(最快,但是每一条配置的sql语句都需要写一次)
方法2、单独配置属性名和列名的对应关系
使用该关系
显然此方法只需要定义一次,开发效率更高。但是要多解析一段xml相对于as更慢。
编写dao实现类的使用方式
1、创建dao实现类IUserDaoImpl,包含参数 SqlSessionFactory
2、编写dao实现方法,方法中参数为mapper的key值,如
public List<User> findAll() {
SqlSession sqlSession = factory.openSession();
List<User> list = sqlSession.selectList("com.jc.dao.IUserDao.findAll");
//关闭资源
sqlSession.close();
return list;
}
3、在测试类中将初始化好的factory对象传递至实现类对象
4、调用方法。
添加操作需注意:
在调用session的insert方法时,注意传递user。
编写dao 实现类的执行过程 分析
使用代理dao方式的执行过程分析
properties标签的使用
作用
-
可以在标签内部配置连接数据库的信息.
-
也可以通过属性引用外部文件的信息
<!--resource
用于指定配置文件的位置,路径按照类的路径写
url:(uniform resource locator) 协议 主机 端口 uri 统一资源定位符,唯一标识一个资源的位置
uri: (uniform resource identifier 统一资源标识符,用于在一个应用内定位一个资源.
-->
<properties url="file:\\\D:\idealProject\day02_eesy_01mabatisCRUD\src\main\resources\jdbcConfig.properties"></properties>
<!--<properties resource="jdbcConfig.properties">-->
<!--</properties>-->
typeAliases标签的使用
typeAliases标签的作用为domain中类配置别名。
<typeAliases>
<!--type属性为要配置别名的类的全限定名, alias为别名,指定了别名后可不区分大小写。如UsEr等-->
<typeAlias type="com.jc.domain.User" alias="user"></typeAlias>
<!--指定要配置别名的包,为该包自动配置别名,且类名就是别名,同样不区分大小写。-->
<package name="com.jc.domain"></package>
</typeAliases>
配置映射文件的位置
<!--配置映射文件的位置-->
<mappers>
<!--<mapper resource="com/jc/dao/IUserDao.xml"></mapper>-->
<!--指定dao接口所在的包,指定后就不需要写mapper 和 resource、class-->
<package name="com.jc.dao"></package>
</mappers>
连接池及事务控制
连接池
1、连接池:实际开发中都会使用连接池,可以减少获取连接所需要的时间。
2、 mybatis的连接池配置在SqlMapConfig.xml中的datesource标签,其中type属性表示的就是采用何种连接池的方式。
type属性值有:
- POOLED 采用传统的javax.sql.DataSource规范中的连接池,mybatis中 有针对规范的实现
- UNPOOLED 采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。
- JNDI 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。注意:如果不是web或者maven的war工程,是不能使用的。
我们课程中使用的是tomcat服务器,采用连接池就是dbcp连接池。
事务
mybatis在oppensession时可以设置事务自动提交,不过一般不采用此种方式。应为只有一次crud操作的时候才适合自动提交。
动态sql语句
1、if标签
2、where标签
3、foreach标签
#和$的区别
一般存在用户输入的时候使用# (不能拼接)
多表查询
- 表之间关系
建立多表之间的关系 - 一对一的情况
1、方法1(不常用),新建实体类,包含两个表中的字段
表关系如图:
现有需求 :查询所有账号信息,并且附带账户所有者姓名和地址
- 新建实体类AccountUser类继承Account类,添加属性username,address。注意toString方法可以在方法体前加上super.toString()
public class AccountUser extends Account {
private String username;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return super.toString() + "AccountUser{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
}
- 配置xml
方法2、利用从表封装主表的信息
例如在account表中封装user的信息:
在IAccount.xml中定义封装account和user的result的resutMap。
- 一对多的情况(一个用户可以拥有多个账户)
主表拥有从表的信息即 user类有 account参数(List集合)
配置映射信息
或者
- 多对多的情况(用户可以有多个角色,一个角色可以有多个用户)
步骤 :
注意当结果集中有两个列名相同时需要起别名,否则封装的时候数据会出错。
JNDL使用
注意要通过tomcat启动
延迟加载、缓存
延迟加载
- 一对一:根据账户查询用户
配置
测试发现并没有延迟加载。
原因是mybatis未配置延迟加载,默认为不延,
配置方法:
官方文档 》 xml配置
在SqlMapConfig中 添加配置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
执行情况
- 一对多:根据用户查询账户,方法类似
<resultMap id="userAccountMap" type="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>
<collection property="accounts" column="id" ofType="account" select="com.jc.dao.IAccountDao.findByUid"></collection>
</resultMap>
<select id="findByUid" resultType="account" parameterType="int">
select * from account where id = #{uid};
</select>
将测试类中原来打印的内容注释
发现已经启用延迟加载(只执行了一个sql语句)
缓存
- 一级缓存(sqlsession范围的缓存)
测试sqlsession缓存
结果
日志中也只发起了一次请求
清空一级缓存的情况:
使用增删改方法自动清空一二级缓存若将flashCache设置为false则不会清空二级缓存
使用commit()会清空一级缓存并提交到二级缓存
- 二级缓存
session在关闭的时候才会同步到二级缓存。
size:查询的次数
执行测试函数。
会发现只发起了一次请求,但是两次的user对象并不是同一个。因为当readOnly为false(默认)时二级缓存并不是缓存对象,而是缓存所有的值。当再次查询时,会通过值重新创建对象。当为true时,则不同。
直接去数据库找
全局关闭一级缓存
注解开发
在模糊查询时,如果不在查询的参数上加%,可以在配置时用如下方法,注意{}里的内容必须是value,且用的是$。
实体类属性和数据库列名对应关系
一对一的查询配置
一对多的配置
注解开发的二级缓存
一级缓存不需要打开,二级需要配置
1、在SqlSessionMap.xml中配置(不配也行,默认是开启的)
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2、在dao中添加注解
只发起了一起请求
但是结果为false,因为二级缓存存储的为对应的信息。需要再次使用时,通过这些信息创建一个对象。
分页
mysql 使用limit 开始,条数
oracle select * from (select t.* , rownum rn from student t where rownum<10) where rn >3
使用插件
导包
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
修改mybatis.xml配置文件
在查询之前使用PageHelper
事务
方式方式1(申明式事务) 通过AOP 在spring配置文件中配置
<!--配置声明式事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager"/>
<!--AOP增强-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.jc.service.impl.*ServiceImpl.*(..))"></aop:advisor>
</aop:config>
方式2(注解式事务)
service.impl 包下的实现类的增、删、改方法上添加 @Transactional 注解,表示开启事务