MyBatis-01
一、Mybatis概述
1. Mybatis简介
1.1 JDBC的问题
- 硬编码问题
- 数据库连接信息的硬编码
- SQL语句和参数硬编码
- 结果集封装硬编码
- 大量的重复代码
1.2 Mybatis介绍
Mybatis是一个优秀的Java轻量级 持久层框架。
- 它内部封装了JDBC,使开发人员只需要关心SQL语句,而不需要处理繁琐的JDBC步骤
- 它采用了ORM思想,解决了实体和数据库映射的问题。只要提供好sql语句,配置了映射,Mybatis会自动根据参数值动态生成SQL,执行SQL并把结果封装返回给我们。
- 它支持XML和注解两种方式配置映射。XML方式使用的更多
1.3 ORM思想
ORM:Object Relational Mapping,对象关系映射思想。指把Java对象和数据库的表和字段进行关联映射,从而达到操作Java对象,就相当于操作了数据库。
二、Mybatis快速入门
1. Mybatis下载
- Mybatis官网:http://www.mybatis.org/mybatis-3/zh/index.html
- Mybatis下载地址:https://github.com/mybatis/mybatis-3/releases/download/mybatis-3.4.6/mybatis-3.4.6.zip
- Mybatis源码下载:https://github.com/mybatis/mybatis-3/archive/mybatis-3.4.6.zip
2. Mybatis快速入门的准备工作
快速入门的功能需求:查询所有用户信息,得到List<User>
-
初始化数据库:执行数据库脚本《资料/mybatisdb.sql》
-
准备开发环境:jdk1.8, Maven和本地仓库,idea
-
准备好Mybatis需要的配置文件约束
- Mybatis核心配置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映射配置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">
3. 快速入门案例(重点)
3.1 创建Maven的java项目:
因为只涉及Dao层,不涉及客户端,所以只要创建Java项目即可。项目坐标信息如下:
groupId: com.viking
artifactId: day46_mybatis01_quickstart
version: 1.0-SNAPSHOT
packing: jar
3.2 在pom.xml中添加依赖
<dependencies>
<!--Junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--MySql的数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--Mybatis的jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--Mybatis依赖的日志包-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
3.3 创建JavaBean: com.viking.bean.User
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
//get/set方法......
//toString方法......
}
3.4 创建dao层的接口:com.viking.dao.IUserDao
在Mybatis里,把dao层的接口称之为映射器(取 调用接口的方法即相当于操作数据库 之意)。
映射器的类名,可以叫XXXMapper
,也可以叫IXXXDao
。我们这里按照之前的习惯,取名IUserDao
注意:只要创建接口即可,不需要创建接口的实现类
public interface IUserDao {
List<User> queryAll();
}
3.5 准备映射配置配置文件xml
注意:
- 映射配置文件名称要和映射器类名一样。例如:映射器叫IUserDao,那么配置文件就叫IUserDao.xml
- 映射配置文件的位置也要和映射器一样。例如:映射器在com.viking.dao里,那么配置文件就应该在resources的com/viking/dao目录下
<?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">
<!--namespace:给哪个接口配置的映射,写接口的全限定类名-->
<mapper namespace="com.viking.dao.IUserDao">
<!--select标签:表示要执行查询语句; id:给接口里哪个方法配置的,写方法名;resultType:结果集封装类型-->
<select id="queryAll" resultType="com.viking.bean.User">
select * from user
</select>
</mapper>
3.6 准备Mybatis的日志配置文件
Mybatis支持使用log4j输出执行日志信息,但是需要我们提供log4j的配置文件:log4j.properties
注意:
- 如果没有log4j.properties,不影响Mybatis的功能,只是没有详细日志而已
- 如果需要日志的话,要把log4j.properties文件放到resources目录下。log4j.properties内容如下:
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:\axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
3.7 准备Mybatis的核心配置文件xml
注意:
- 核心配置文件的名称随意,我们习惯叫 SqlMapConfig.xml
- 核心配置文件的位置随意,我们习惯放到resources目录下
<?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_mybatis">
<!--定义一个数据库连接环境-->
<environment id="mysql_mybatis">
<!--设置事务管理方式,固定值JDBC-->
<transactionManager type="JDBC"/>
<!--设置数据源,POOLED,UNPOOLED,JNDI,我们使用POOLED-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis49"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- !!!配置映射文件的位置!!!!!!!!!!!!!!这一步千万不要忘记!!!!!!!!!!!!!! -->
<mappers>
<mapper resource="com/viking/dao/IUserDao.xml"/>
</mappers>
</configuration>
3.7 编写测试代码
@Test
public void testQuickStart() throws IOException {
//1. 读取核心配置文件SqlMapConfig.xml
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 创建SqlSessionFactoryBuilder构造者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3. 使用构造者builder,根据配置文件的信息is,构造一个SqlSessionFactory工厂对象
SqlSessionFactory factory = builder.build(is);
//4. 使用工厂对象factory,生产一个SqlSession对象
SqlSession session = factory.openSession();
//5. 使用SqlSession对象,获取映射器IUserDao接口的代理对象
IUserDao dao = session.getMapper(IUserDao.class);
//6. 调用IUserDao代理对象的方法,查询所有用户
List<User> users = dao.queryAll();
for (User user : users) {
System.out.println(user);
}
//7. 释放资源
session.close();
is.close();
}
三、自定义Mybatis(了解)
在这一章,我们将要使用前边学习过的知识,尝试自定义一个Mybatis框架出来,以便加深对Mybatis的理解。自定义框架涉及的知识点有:构造者模式、工厂模式、代理模式、反射、注解、XML解析、XPath表达式等等。
1. Mybatis框架使用了哪些设计模式(面试)
《设计模式之禅》
1.1 构造者模式
- 使用SqlSessionFactoryBuilder,根据核心配置文件,构造一个SqlSessionFactory对象出来
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
- 构造者模式:用于构造一个对象。它的重点在于 根据配置文件、或者参数,定制一个对象出来。
- 好处:可以把定制对象的复杂过程隐藏起来, 只要调用一个方法,把配置文件/参数传递进去,就可以得到根据参数定制的对象。
1.2 工厂模式
- 使用SqlSessionFactory,生产一个SqlSession对象出来
SqlSession session = factory.openSession();
- 工厂模式:用于代替new操作的一种模式,是一种最常用的实例化对象的模式了。
- 好处:可以降低程序之间的耦合性,提高应用的可扩展性,在功能维护时尽量少的代码修改
1.3 代理模式
- 使用SqlSession获取IUserDao的代理对象,通过代理对象实现增、删、改、查操作
IUserDao dao = session.getMapper(IUserDao.class);
- 代理模式:在某些情况下,一个对象不适合或者不能直接调用另外一个对象,就可以使用代理对象作为中介来间接调用。
- 好处:
- 职责清晰。被代理对象只要完成自己的业务逻辑,不需要关心其它非本职事务;通过代理对象来完成功能的扩展。附带的后果就是编程简洁、清晰
- 高扩展性。
2. 自定义Mybatis框架的分析
2.1 JDBC的执行流程
2.2 基于JDBC的封装流程分析
Mybatis框架中,最核心的一个对象就是SqlSession对象。这个对象本身提供了执行增、删、改、查的方法,同时也提供了获取接口代理对象的方法,通过代理对象实现增、删、改、查。
我们自定义Mybatis时,最关键的一步也是SqlSession对象。但是SqlSession对象不能直接new出来,需要读取配置文件,并使用一系列的设计模式生产出来。过程如下:
- 读取配置文件,封装成Configuration对象
- 使用构造者模式、工厂模式 生产出SqlSession对象
- SqlSession对象使用代理模式,创建接口的代理对象,由代理对象完成增、删、改、查操作
2.3 自定义Mybatis的准备工作
- 创建Maven的Java项目
- 引入坐标依赖:引入MySql驱动、Junit、dom4j、jaxen,无需再引入Mybatis和log4j
- 准备JavaBean和映射器IUserDao
- 准备映射配置文件和核心配置文件
- 把快速入门中的测试代码拷贝过来
3. 自定义Mybatis框架的实现
3.1 配置文件的读取和封装
- 配置信息相关类介绍
- Mapper:接口中每个方法的配置信息对象,包含了方法要执行的SQL语句、返回类型
- Configuration:配置信息对象,包含了数据库连接信息,和所有接口中所有方法的Mapper信息
- 创建一个XMLConfigParser类,用于读取配置文件封装成Configuration对象
public class XMLConfigParser {
public Configuration parse(InputStream is) throws DocumentException, IOException {
Configuration configuration = new Configuration();
SAXReader reader = new SAXReader();
Document document = reader.read(is);
//1. 读取核心配置文件的信息
//1.1 读取数据库驱动信息
Element element = (Element) document.selectSingleNode("//property[@name='driver']");
String driver = element.attributeValue("value");
configuration.setDriver(driver);
//1.2 读取数据库url信息
element = (Element) document.selectSingleNode("//property[@name='url']");
String url = element.attributeValue("value");
configuration.setUrl(url);
//1.3 读取数据库的username
element = (Element) document.selectSingleNode("//property[@name='username']");
String username = element.attributeValue("value");
configuration.setUsername(username);
//1.4 读取数据库的password
element = (Element) document.selectSingleNode("//property[@name='username']");
String password = element.attributeValue("value");
configuration.setPassword(password);
//2. 读取所有的映射配置文件信息
List<Node> nodes = document.selectNodes("//mappers/mapper");
for (Node node : nodes) {
Element mapperElement = (Element) node;
String mapperResource = mapperElement.attributeValue("resource");
if (mapperResource != null) {
Map<String, Mapper> mappers = loadMappers(mapperResource);
configuration.setMappers(mappers);
}
}
return configuration;
}
private Map<String, Mapper> loadMappers(String mapperResource) throws IOException {
Map<String, Mapper> mappers = new HashMap<>();
InputStream is = null;
try {
is = Resources.getResourceAsStream(mapperResource);
SAXReader reader = new SAXReader();
Document document = reader.read(is);
Element rootElement = document.getRootElement();
//1. 获取根标签上的namespace属性值-----映射器接口的全限定类名
String daoClassName = rootElement.attributeValue("namespace");
//2. 获取接口里每个方法的配置信息
List<Element> elements = rootElement.elements();
for (Element element : elements) {
//2.1 获取方法的名称
String daoMethodName = element.attributeValue("id");
//2.2 获取方法的SQL语句
String sql = element.getText();
//2.3 获取方法的返回值类型
String resultType = element.attributeValue("resultType");
//2.4 把这个方法的SQL语句和返回值类型,封装成一个Mapper对象
Mapper mapper = new Mapper();
mapper.setResultType(resultType);
mapper.setSql(sql);
//2.5 把Mapper对象放到 Map<String, Mapper> mappers里。key是接口类名+方法名
String key = daoClassName+"."+ daoMethodName;
mappers.put(key, mapper);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
is.close();
}
return mappers;
}
}
3.2 DefaultSqlSession对象的创建
- 相关类介绍
- SqlSessionFactoryBuilder:构造者 类,用于根据配置文件,构造工厂对象SqlSessionFactory
- SqlSessionFactory接口 和 实现类DefaultSqlSessionFactory:工厂 类,用于生产SqlSession对象
- SqlSession接口 和 实现类DefaultSqlSession: 我们需要的就是这个对象,用于获取代理对象
- DefaultSqlSession对象的创建过程分析
- 通过构造者SqlSessionFactoryBuilder,根据配置文件,使用build方法构造一个工厂对象DefaultSqlSessionFactory
- 通过工厂DefaultSqlSessionFactory,使用openSession()方法,生产一个DefaultSqlSession对象
- 创建SqlSession接口 和 实现类DefaultSqlSessionFactory
public interface SqlSession{
}
public class DefaultSqlSession implements SqlSession{
}
- 创建SqlSessionfactory接口 和 实现类DefaultSqlSessionFactory
public interface SqlSessionFactory{
}
public class DefaultSqlSessionFactory implements SqlSessionFactory{
}
- 创建SqlSessionFactoryBuilder,提供方法build(is)
public class SqlSessionFactoryBuilder{
public SqlSessionFactory build(is){
//1. 读取配置文件is的内容,封装成Configuration对象(使用3.2章节中的XMLConfigParser)
XMLConfigParser parser = new XMLConfigParser();
Configuration configuration = parser.parse(is);
//2. 根据configuration对象,构造一个DefaultSqlSessionFactory对象
return new DefaultSqlSessionFactory(configuration);
}
}
- 修改DefaultSqlSessionFactory的代码
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration configuration;
public DefaultSqlSessionFactory(){}
/**
* 使用构造方法,把configuration对象赋值给成员变量
* 注意:configuration对象,是给SqlSession使用的,工厂类自己不用
*/
public DefaultSqlSessionFactory(Configuration configuration){
this.configuration = configuration;
}
/**
* 提供一个生产SqlSession对象的方法
* 把configuration对象传递给SqlSession对象
*/
public SqlSession openSession(){
return new DefaultSqlSession(configuration);
}
}
- 修改DefaultSqlSession的代码
public class DefaultSqlSession implements SqlSession{
private Configuration configuration;
public DefaultSqlSession(){}
/**
* 使用构造方法,把configuration对象赋值给成员变量
*/
public DefaultSqlSession(Configuration configuration){
this.configuration = configuration;
}
}
3.3 生成代理对象让代理对象执行SQL封装结果
- 修改DefaultSqlSession的代码,增加getMapper方法,用于生成接口的代理对象
public class DefaultSqlSession implements SqlSession{
private Configuration configuration;
private Connection connection;
public DefaultSqlSession(){}
/**
* 使用构造方法,把configuration对象赋值给成员变量
*/
public DefaultSqlSession(Configuration configuration){
this.configuration = configuration;
// 获取连接:本身应该是使用连接池,从连接池中获取。这里简化处理,直接获取连接
Class.forName(configuration.getDriver());
this.connection = DriverManager.getConnection(configuration.getUrl(), configuration.getUsername(), configuration.getPassword());
}
/**
* 根据dao接口的字节码对象,生成代理对象。
*
*/
public <T> T getMapper(Class daoInterfaceClass){
return (T) Proxy.newProxyInstance(
daoInterfaceClass.getClassLoader(),
new Class[]{daoInterfaceClass},
new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args){
//这里写代理对象的行为:使用Executor执行SQL语句。
//要使用Executor的selectList方法执行SQL语句,需要的参数:
//1. 数据库连接对象--构造方法中已经得到了连接对象 this.connection
//2. SQL语句和结果集封装类型--Mapper对象---从Configuration的map里取
String key = daoInterfaceClass.getName()+"."+method.getName();
Mapper mapper = configuration.getMappers().get(key);
return Executor.selectList(connection,mapper);
}
}
);
}
}
- 修改DefaultSqlSession的代码,增加close()方法,用于关闭连接
public class DefaultSqlSession implements SqlSession{
private Configuration configuration;
private Connection connection;
public DefaultSqlSession(){}
public DefaultSqlSession(Configuration configuration){
..............
}
public <T> T getMapper(Class daoInterfaceClass){
..............
}
/**
* 释放资源
*/
public void close(){
this.connection.close();
}
}
3.4 测试自定义的Mybatis
运行测试代码,验证自定义的Mybatis能否正常运行