MyBatis框架安排
目录
1. 第一天:
1. MyBatis入门
- 什么是框架?
答:它是我们软件开发过程中的一套解决方案, 不同的框架解决不同的问题。
简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别 人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。 - 三层框架
- 表现层:control
- 业务逻辑层:service
- 持久层:dao
可以看到不同的框架各司其职,在软件开发中担任不同的角色,MyBatis框架主要是与数据库打交道,所以它工作在持久层。
- 持久层的传统解决方案
- JDBC技术
- Connection:连接数据库对象
- PrepareStatement:预处理对象,用于操作sql
- ResultSet:操作数据库后生成的结果集
- Spring的SpringTemplate技术
Spring中对jdbc的简单封装 - Apache的DBUtils
和SpringTemplate类似,Apache中对jdbc的简单封装
- JDBC技术
以上所用都不是框架
JDBC是接口规范
SpringTemplate和DBUtils都是对jdbc接口规范的实现类,是一个工具类。
传统的jdbc访问数据库:
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//通过驱动管理类获取数据库链接
connection =DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","ro ot", "root");
//定义 sql 语句 ?表示占位符
String sql = "select * from user where username = ?";
//获取预处理 statement
preparedStatement = connection.prepareStatement(sql);
//设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的 参数值
preparedStatement.setString(1, "王五");
//向数据库发出 sql 执行查询,查询出结果集 resultSet = preparedStatement.executeQuery();
//遍历查询结果集
while(resultSet.next()){
System.out.println(resultSet.getString("id")+" "+resultSet.getString("username"));
}
} catch (Exception e) {
e.printStackTrace();
}
finally{
//释放资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
上边使用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作。
PreparedStatement对象它的执行方法
- execute:可以执行任何CRUD语句,返回值是一个布尔值,表示是否有结果。
- excuteUpdate:只能执行CUD,查询语句无法执行,返回值数据库操作影响的行数。
- excuteQuery:只能执行Select 语句,无法进行增删改操作,执行的结果集为ResultSet对象
2. MyBatis概述
-
概念:mybatis是一个持久层框架,用Java编写,它封装了jdbc的很多细节,使开发者能够只关注sql语句本身,无需关注注册驱动,创建连接等繁琐的过程,它使用了ORM思想实现了结果集的封装。
ORM思想:
Object Relational Mapping对象关系映射
简单的说就是实体类的属性和数据库表的字段产生了对应关系,使得我们操作实体类就可以操作数据库表了。
例如:user表 User类 username varchar(32) String username id int(12) int id grade varchar(12) String grade
3. MyBatis环境搭建
-
环境搭建步骤:
- 创建maven工程并导入坐标
- 创建实体类和dao接口
- 创建MyBatis的主配置文件:SqlMapConfig.xml
- 创建dao层映射配置文件:IUserDao.xml
-
注意事项:
- 映射配置文件还有一种写法是Mapper.xml。我们写成IUserDao.xml主要是为了和之前写过的dao做一个过渡。
- 在里面创建目录时,它和创建包是不一样的。例如
创建目录:com.liuzeyu.dao 它只是一级目录,在硬盘上也是一级目录
创建包:com.liuzeyu.dao 它只是三级目录,分层显示,在硬盘上也是三级目录 - mybatis的映射配置文件必须和dao层的包结构相同,映射配置文件写在resource资源文件下
- 映射配置文件的mapper标签下的namespace属性取值必须是dao层接口的全限定类名
- 映射配置文件的操作配置,如select的id属性取值必须是dao层接口的方法名
当我们遵从了3,4,5点后,就可以不用像从前那样写dao接口的实现类了。
4. MyBatis入门案例
-
在搭建完环境后,完成入门案例。首选创建实体类和MySQL数据库和表,并生成相应的getter和setter方法
sql代码:DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL auto_increment, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` datetime default NULL COMMENT '生日', `sex` char(1) default NULL COMMENT '性别', `address` varchar(256) default NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');
-
项目对象模型(pom.xml)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.liuzeyu</groupId> <artifactId>day01_mybatis</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> </dependencies> </project>
-
dao层接口
package com.liuzeyu.dao; import com.liuzeyu.domain.User; import java.util.List; public interface IUserDao { public List<User> findAll(); }
-
SqlMapConfig.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="mysql"> <!-- 配置mysql环境 --> <environment id="mysql"> <!-- 配置事物类型 --> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据库源(连接池)--> <dataSource type="POOLED"> <!-- 配置连接数据库的四个基本信息 --> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis" /> <property name="username" value="root" /> <property name="password" value="809080" /> </dataSource> </environment> </environments> <!-- 指定映射配置文件的位置,映射配置文件是指每一个独立的dao接口配置--> <mappers> <mapper resource="com.liuzeyu.dao.IUserDao.xml"/> </mappers> </configuration>
-
映射文件IUserDao.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.liuzeyu.dao.IUserDao"> <select id="findAll"> SELECT * from user </select> </mapper>
-
编写测试类MyBatisTest.java
public class MyBatisTest { /** * mybatis入门案例测试 * @param args */ public static void main(String[] args) throws Exception{ //1.读取配置文件SqlMapConfig.xml InputStream is = MyBatisTest.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //3.使用factory创建SqlSession SqlSession session = factory.openSession(); //4.使用SqlSessionFactory创建dao层接口的动态代理对象 IUserDao dao = session.getMapper(IUserDao.class); //5.使用代理对象执行方法 List<User> users = dao.findAll(); //6.遍历输出结果 for (User user : users) { System.out.println(user); } //释放资源 is.close(); session.close(); } }
-
运行后出现
翻译过来,没有找到IUserDao.xml,考虑到映射文件是在SqlMapConfig.xml上配置的,故检查SqlMapConfig.xml
发现:
改成:
再次运行又发现:
这个问题是未在接口的映射配置文件中定义resultType导致的,resultType是操作数据库返回封装结果集的对象
添加后:
-
测试输出:
设计到的设计模式分析(涉及到构建者模式,工厂模式,代理模式简单看一下,下面还会学习到) -
查看测试类方法,发现代码也比较复杂,涉及到多个对象,如SqlSessionFactoryBuilder,SqlSessionFactory…其实MyBat也提供了更为简洁的实习方式,即,注解配置,这样虽然不能减少测试类的代码编写,但是可以直接删除掉映射接口的配置文件,采用注解配置,将原来的配置文件删除
重新在接口的定义处添加
感觉简单了很多,少了配置文件,但是需要在Mybatis的主配置文件中修改映射文件的配置,将resource属性改为class属性,并添加接口的全限定类名
重新运行程序:
-
其实MyBatis也提供了接口实现类的方法来与数据库交互,只需要对dao层添加接口实现类,可以在9的基础上进行改造
添加dao层的接口实现:
测试函数部分就可以省去SqlSession对象的建立
小结:
通过学习mybatis的案例可以发现,比我们之前使用的jdbc简单很多,可以省去接口实现类和jdbc初始化等一系列操作,添加两个配置文件即可,再者可以通过注解配置,使得与数据库的交互更加的简单。
但是这里面包含了很多的细节,为什么会有工厂对象SqlSessionFactory,为什么有了工厂对象后还需要有构建者对象SqlSessionFactoryBuilder,为什么IUserDao.xml 在创建的时候有文件名和位置的规定。通过下一章的学习可以明白mybatis的内部执行流程,并且可以对这些设计模式有一个认识。
<mark>注意事项</mark>
- 不要忘记在接口的映射配置文件中告知mybatis要封装到哪个实体类中,配置的方式:指定实体类的全限定类名。
- 如果使用注解配置,需要在主配置文件处修改mapper属性为class属性
5. 自定义MyBatis框架(深入了解MyBatis执行细节)
-
分析在上述案例中的测试类selectList是如何运行的?
List< E > selectList(PreparedStatement statement)
当代理对象过程中,调用了Proxy.newInstanceProxy(类加载器,代理对象要实现的接口字节码数组,增强代码),在增强代码部分进行selectList的调用。
先不谈代理模式,来看看selectList都是怎么进行的呢?- 首先程序通过xml解析技术对SqlMapConfig.xml进行读取,获取到4个连接数据库的必备信息和映射接口配置文件的路径
- 有了driver,url,username,password就可以在selectList中创建Connection对象
- 通过配置文件的映射关系可以得到真正执行的sql语句和结果集的全限定类名。
- 有了sql就可以获取到PreparedStatement 的preparedStatement对象了,prepareStatement = connection.prepareStatement(sql);
- 有了结果集的全限定类名接口就可以通过反射机制ClassforName(“全限定类名”).newInstance()得到真实的返回值类型对象
- 有了4,5两对象就可以通过遍历结果集来将结果存入准备好的list集合中并返回
问题:
基于4,5,如何将<mapper namespace="com.liuzeyu.dao.IUserDao"> <select id="findAll" resultType="com.liuzeyu.domain.User"> SELECT * from user </select> </mapper>
发送到selectList,需要将映射信息封装成一个Mapper对象,这是一个Map对象
key(String) value(Mapper) com.liuzeyu.dao.findAll 全限定类名+sql语句 这样做的好处是,如果以后会有多个查询findById,findByName等一系列方法可以直接和全限定类名找到对应关系。
-
问题2:内部的SqlSession创建代理对象的执行流程
//4.使用SqlSessionFactory创建dao层接口的动态代理对象 IUserDao dao = session.getMapper(IUserDao.class);
核心是通过getMapper(IUserDao.class);来获取代理对象的
public <T> getMapper(Class<T> daoInterfaceClass){
/**
类加载器:它使用的和被代理对象相同的类加载器
代理对象要实现的接口:它使用的和被代理对象实现相同的接口
如何代理:增强方法,需要我们字节来提供
此处是一个InvocationHandler的接口,我们需要写一个该接口的实现类,在实现类中调用selectList方法
*/
Proxy.newProxyInstance(类加载器,代理对象要实现的接口字节码数组,如何代理);
}
- 自定义代理模式
-
分析:
- mybatis使用dao代理的方式实现增删改操作时做什么事?
两件事:- 创建代理对象
- 在代理对象中调用selectList
- mybatis使用dao代理的方式实现增删改操作时做什么事?
-
自定义mybatis 所需要的类
1. Class Resource
2. Class SqlSessionFactoryBuilder
3. interface SqlSessionFactory
4. interface SqlSession -
由于涉及的代码较为复杂,故将项目up到git上面
https://github.com/liuzeyu12a/mybatis-
-