Java框架-mybatis-基础
1. 概念
1.1 概念引入
- 框架( Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法。或者说是可被应用开发者定制的应用骨架。
- 框架的优点:
- 框架已经实现了一些功能,使用时不需重复实现这些功能,提高开发效率;
- 使用框架后,我们软件的架构更加稳定、开发流程必须按照框架约定进行,而优秀的框架是所有开发人员都熟悉的,任何人进入项目团队后可以更快适应开发,同时框架良好的扩展性更易于后期维护,减少项目成本。
1.2 javaweb常用框架
表现层:SpringMVC、Struts2
业务层:Spring可对service层提供统一的事务控制。
数据访问层:JDBC、自定义框架封装jdbc、JDBCTemplate(Spring)、apache DbUtils、Hibernate框架、Jpa、Spring Data Jpa、Mybatis等
1.3 传统JDBC开发弊端
-
频繁创建连接对象和释放,容易浪费系统资源影响性能;
-
sql语句的定义、参数设置、结果集处理存在硬编码,不好维护和修改
. (3) 结果集处理存在重复代码,每次都要遍历ResultSet,获取一行数据,封装为对象处理麻烦。
2. MyBatis框架
-
mybatis早期版本叫做ibatis,目前代码托管在github。
-
mybatis是对jdbc的封装,是一个持久层的框架。开发者只需要关注业务本身,不需要花费精力去处理其他过程代码
-
mybatis是通过xml或者注解进行配置,实现java对象与sql语句的对应关系(映射)。
3. MyBatis框架-构建项目
3.1 准备环境
1.准备数据库
-- 1.创建数据库
CREATE DATABASE mybatis DEFAULT CHARACTER SET utf8;
-- 2.创建用户表
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-08-27 17:47:08','男','北京'),(42,'小二王','2018-09-02 15:09:37','女','南京'),
(43,'明明','2018-01-04 11:34:34','女','东京'),
(45,'狗蛋','2017-02-04 12:04:06','男','西安'),
(46,'隔壁老王','2015-05-07 17:37:26','男','济南'),
(48,'茱莉','2017-07-07 11:44:00','女','华盛顿');
SELECT * FROM USER;
2.创建项目
3.创建实体类
package com.azure.entity;
import java.util.Date;
public class User {
private int id;
private String username;
private Date birthday;
private String sex;
private String address;
public User() {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
public User(int id, String username, Date birthday, String sex, String address) {
this.id = id;
this.username = username;
this.birthday = birthday;
this.sex = sex;
this.address = address;
}
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username;}
public Date getBirthday() {return birthday;}
public void setBirthday(Date birthday) {this.birthday = birthday;}
public String getSex() {return sex;}
public void setSex(String sex) {this.sex = sex;}
public String getAddress() {return address;}
public void setAddress(String address) {this.address = address;}
}
4.添加依赖
<?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.azure</groupId>
<artifactId>day47projects_mybatis01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!--mybatis支持包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!--数据库驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.30</version>
</dependency>
<!--日志包-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>
3.2 配置SqlMapConfig.xml
- 该文件要放在src/main/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">
<configuration>
<!--default 表示默认使用哪一个运行环境配置-->
<environments default="mysql">
<!--id="mysql" 表示mysql的运行环境配置-->
<environment id="mysql">
<!--事务管理器配置:基于JDBC的事务控制-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据库连接池方式,type值有三个UNPOOLED,POOLED,JNDI
unpooled,不使用连接池
pooled,使用mybatis内置数据库连接池(推荐使用,与mybatis配合使用性能最好)
JNDI,使用javaee容器(服务器)内置的数据库连接池
-->
<dataSource type="pooled">
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///mybatis?characterEncoding=utf8"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</dataSource>
</environment>
</environments>
<!--加载接口的映射文件-->
<mappers>
<!--必须放到类路径下-->
<mapper resource="com/azure/dao/IUserDao.xml"></mapper>
<!--注意:如果这里是/,那么在resources创建文件夹时也要对应的写成"com/azure/dao",而不能用"."分隔!!-->
</mappers>
</configuration>
3.3 dao层
3.3.1 面向接口编程,创建接口
/*
数据访问层接口
*/
public interface IUserDao {
List<User> findAll();
}
3.3.2 dao接口映射
-
接口映射文件相当于dao接口的实现类!
-
注意:映射文件存放目录要与SqlMapConfig.xml加载映射的路径要一致。如果SqlMapConfig.xml是用/分隔路径,那么再创建映射文件的存放目录时也一定要以/分隔,否则会报错无法找到映射文件!!原因详见“3.6节 注意事项”
<?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.azure.dao.IUserDao">
<!--
select:代表实现查询操作
id:用于设置实现接口中哪个方法
resultType:用于设置接口方法返回的泛型类型
标签体:执行的sql语句
-->
<select id="findAll" resultType="com.azure.entity.User">
select * from user
</select>
</mapper>
3.4 引入日志文件
- 日志文件 log4j.properties要放在src/main/resources目录下
- 注意:
# 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.5 dao层测试类
public class UserDaoTest {
public static void main(String[] args) throws Exception {
//1.获取文件流
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建工厂构造器
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.根据文件和构造器创建工厂
SqlSessionFactory factory = builder.build(is);
//4.使用工厂创建SqlSession对象
SqlSession sqlSession = factory.openSession();
//5.创建接口代理对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
//查看是否代理对象
System.out.println(userDao.getClass());
//6.调用方法
List<User> users = userDao.findAll();
System.out.println(users);
//7.关闭并释放资源
sqlSession.close();
is.close();
}
}
3.6 注意事项
-
需要主配置(SqlMapConfig.xml)加载接口映射文件路径;
-
映射文件的namespace对应接口路径(相当于实现类);
-
接口的方法与接口对应映射的方法名称要一一对应:
-
在IDEA内resources目录下创建文件夹与java目录下创建文件夹不同。java目录下创建多级目录可以用".“分隔,比如
com.azure.dao
是三级目录。而在resources目录下创建多级目录只能用”/“分隔,如果仍以”."分隔,比如com.azure.dao
,实际上只是创建一个文件夹,文件夹名就是com.azure.dao。
4. 自定义框架(了解)
系统架构分析:
最后是使用dao的代理对象查询数据库,首先得生成代理对象,分析如下:
- 先对指定的dao接口生成代理对象,通过sqlSession对象调用方法获得:
<T> T getMapper(Class<T> daoClass);
; - 获得sqlSession对象,对SqlSession接口进行实现
- 而SqlSession是通过SqlSessionFactory工厂创建的,故要先定义工厂
4.1 SqlSession接口、DefaultSqlSession接口默认实现类
4.1.1 SqlSession接口
/*
SqlSession接口定义
*/
public interface SqlSession {
/**
* 根据指定的dao接口获得代理对象
* @param daoClass
* @param <T>
* @return dao代理对象
*/
<T> T getMapper(Class<T> daoClass);
}
4.1.2 DefaultSqlSession接口默认实现类
public class DefaultSqlSession implements SqlSession {
/**
* 根据指定的dao接口获得代理对象
*
* 使用jdk动态代理proxy
* @param daoClass
* @return dao代理对象
*/
public <T> T getMapper(Class<T> daoClass) {
/*
代理实现方式:
静态代理
jdk动态代理
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
1.ClassLoader loader:类加载器
2.Class<?>[] interfaces:需要代理的接口类对象数组(代理IUserDao获取动态代理虚拟实现对象)
3.InvocationHandler h:事件处理程序,当执行代理对象方法时候会触发事件处理程序,把当前方法传入事件处理程序中。
cglib代理(spring)
*/
return (T) Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),new Class[]{daoClass},new MapperProxy());
}
}
4.1.3 SqlSessionFactory工厂
public class SqlSessionFactory {
/*
创建SqlSession
*/
public static SqlSession openSession(){
return new DefaultSqlSession();
}
}
4.1.4 MapperProxy代理 (事件处理程序)
public class MapperProxy implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.获取当前执行的方法的名称
String methodName = method.getName();
//2.获得方法所属类全名
String className = method.getDeclaringClass().getName();
//3.获取映射配置文件数据,由于映射文件数据内容较多,而且映射文件数目也多
// 可以使用一个类(Mapper)将每个映射文件数据封装起来,然后再用一个Map将所有映射文件的Mapper对象封装
Map<String, Mapper> mappers = Configuration.getInstance().getMappers();
//获取当前方法的映射配置
Mapper mapper = mappers.get(className + "." + methodName);
//4.执行sql命令并返回数据
//判断sql语句类型
if (mapper.getSqlType().equalsIgnoreCase("select")) {
//使用工具类执行sql
return Executor.selectList(mapper);
}
return null;
}
}
4.1.5 Executor工具类
/*
执行sql语句工具类
*/
public class Executor {
//获取数据库连接池
private static DataSource ds = Configuration.getDataSource();
public static Object selectList(Mapper mapper) {
//获取sql
String sql = mapper.getSql();
//获取返回值类型
String resultType = mapper.getResultType();
/*
目标:执行sql语句,封装数据到List<>返回
*/
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
//返回集合数据,使用泛型
List list = new ArrayList<>();
try {
conn = ds.getConnection();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
//获取结果集元数据
ResultSetMetaData metaData = rs.getMetaData();
//通过元数据获取列数
int columnCount = metaData.getColumnCount();
//获取返回类型的字节码类型对象(用于后面获取改类的对象实例封装数据)
Class resultTypeClazz = Class.forName(resultType);
//迭代结果集
while (rs.next()) {
//实例返回对象
Object obj = resultTypeClazz.getConstructor().newInstance();
//循环获取每条数据封装
for (int i = 1; i <= columnCount; i++) {
//根据列索引获取列名
String columnName = metaData.getColumnName(i);
//根据列索引获取每个字段的值
Object value = rs.getObject(i);
//使用反射根据属性名和字节码对象获得属性描述器对象
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClazz);
//根据属性描述器获取set封装方法
Method setMethod = propertyDescriptor.getWriteMethod();
//将列名和值封装对对象中,利用对象set方法对象的invoke执行封装
setMethod.invoke(obj,value);
}
//添加数据到集合中
list.add(obj);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
rs.close();
ps.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
}
4.1.6 Configuration解析并封装配置文件数据
/*
使用Configuration解析并封装配置文件数据
*/
public class Configuration {
//使用单例模式创建对象并解析数据,确保当前对象被实例化一次
//1. 创建静态类对象
private static Configuration configuration = null;
//2.定义私有构造函数
private Configuration() {
//在实例化的时候解析配置文件数据并封装
//加载主配置文件数据SqlMapConfig.xml
loadSqlMapConfig();
}
//3.提供共有静态方法返回对象
public static Configuration getInstance() {
//只有调用方法才会创建对象,后面调用所获取的都是同一个对象(即第一次创建的对象)--单例模式的选择原因
if (configuration == null) {
configuration = new Configuration();
}
return configuration;
}
/**
* 用于加载主配置文件
* 使用jsoup对xml文件进行解析
*/
private void loadSqlMapConfig() {
try {
//1.根据文件路径获取xml的Document对象
String path = Resources.class.getResource("/SqlMapConfig.xml").getPath();//使用类字节码对象获取文件路径
Document document = Jsoup.parse(new File(path), "utf-8");
/*获取Document对象的数据并封装数据库连接的4个数据到configuration对象中
原因:所有的配置文件数据都封装到configuration对象中,数据库连接可以通过configuration对象获取配置文件中所需的这4个数据
*/
this.driver = document.selectFirst("property[name='driver']").attr("value");
this.url = document.selectFirst("property[name='url']").attr("value");
this.username = document.selectFirst("property[name='username']").attr("value");
this.password = document.selectFirst("property[name='password']").attr("value");
//2.加载接口映射配置文件
//2.1 获取mapper标签列表,循环遍历加载
Elements mapperElements = document.select("mapper");
for (Element mapperElement : mapperElements) {
String mapperPath = mapperElement.attr("resource");
//加载每个映射接口配置文件
loadMapperData(mapperPath);
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 加载当前接口配置文件的所有数据到mappers中
* @param mapperPath
*/
private void loadMapperData(String mapperPath) throws IOException {
//根据文件路径获取Document对象
String path = Resources.class.getResource("/" + mapperPath).getPath();//注意:这里必须拼接/
Document document = Jsoup.parse(new File(path),"utf-8");
//解析数据
Element mapperElement = document.selectFirst("mapper");
String namespace = mapperElement.attr("namespace");
Elements childrenElements = mapperElement.children();
//遍历子标签封装数据
for (Element childrenElement : childrenElements) {
String id = childrenElement.attr("id");
String resultType = childrenElement.attr("resultType");
String sql = childrenElement.text();
String sqlType = childrenElement.nodeName();
//将数据封装到Mapper里面
Mapper mapper = new Mapper();
mapper.setNamespace(namespace);
mapper.setId(id);
mapper.setResultType(resultType);
mapper.setSql(sql);
mapper.setSqlType(sqlType);
//将Mapper封装mappers(Map<String,Mapper>)里面
//Map的键=接口类全名.方法名
//map的值=映射文件的方法实现标签,也就是Mapper类
mappers.put(namespace + "." + id, mapper);
}
}
/**
* 解析配置文件并封装到当前类中
*/
//1.定义configuration对象的成员变量,用于封装数据库连接字符串信息
private String driver;
private String url;
private String username;
private String password;
//2.定义成员方法
//2.1 提供公共静态的方法返回数据库连接池对象
public static DataSource getDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
//获取Configuration实例对象,此时Configuration对象第一次实例
Configuration configuration = getInstance();
//设置数据库信息
druidDataSource.setDriverClassName(configuration.driver);
druidDataSource.setUrl(configuration.url);
druidDataSource.setUsername(configuration.username);
druidDataSource.setPassword(configuration.password);
return druidDataSource;
}
//2.2 由于主配置文件中可以关联多个接口映射文件数据,需要用集合封装所有接口映射文件
//Map一个键值对可以封装一个映射文件数据,所以使用Map集合
//Map的键=接口类全名.方法名
//map的值=映射文件的方法实现标签,也就是Mapper类
private Map<String,Mapper> mappers = new HashMap<>();
public Map<String,Mapper> getMappers(){
return mappers;
}
//getter&setter
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
4.1.7 dao层接口和dao配置文件
接口
/*
数据访问层接口
*/
public interface IUserDao {
List<User> findAll();
}
配置文件
<?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.azure.dao.IUserDao">
<!--
select:代表实现查询操作
id:用于设置实现接口中哪个方法
resultType:用于设置接口方法返回的泛型类型
标签体:执行的sql语句
-->
<select id="findAll" parameterType="int" resultType="com.azure.entity.User">
select * from user
</select>
</mapper>