1.初识Mybatis
Mybatis是一个优秀的基于Java的持久层框架,它内部封装了Jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。
Mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由Mybatis框架执行sql并将结果映射为java对象并返回。
采用ORM思想(对象关系映射)解决了实体和数据库映射的问题,对Jdbc进行了封装,屏蔽了Jdbc Api底层访问细节,使我们不用与Jdbc Api打交道,就可以完成对数据库的持久化操作。
ORM :操作对象既是操作数据库
2.Mybatis框架
2.1首先来看架构:
2.2架构细节:
从图中可以看出XXXMapper.xml中namespace+id作为唯一标识,它就是Map中的key
按照上面的架构,我们可以来自定义一个Mybatis框架:
引入需要的依赖:
<?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.itheima</groupId>
<artifactId>mybatis_day01_2_custom_frame</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.9</maven.compiler.source>
<maven.compiler.target>1.9</maven.compiler.target>
</properties>
<dependencies>
<!--引入dom4j的jar包: document for java -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
<exclusions>
<exclusion>
<artifactId>xml-apis</artifactId>
<groupId>xml-apis</groupId>
</exclusion>
<exclusion>
<artifactId>xml-apis</artifactId>
<groupId>xml-apis</groupId>
</exclusion>
</exclusions>
</dependency>
<!--
在idea中,使用dom4j依赖时 ,必须依赖此jar包
-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>
</project>
xml文件,编程时看着用,用完打jar包时删掉:
SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatisdb?characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/itheima/mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
UserMapper.xml
<?xml version="1.0" encoding="utf-8" ?>
<mapper namespace="com.itheima.dao.UserDao">
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
</mapper>
Configuration.java
package frame.domain;
import java.util.HashMap;
import java.util.Map;
public class Configuration {
private String url;
private String username;
private String password;
private String driverClass;
/**
* 第一个泛型:String:唯一的标识:mapperId
* 第二个泛型:Mapper:存储sql + resultType
*/
private Map<String ,Mapper> xmlMap = new HashMap<>();
省略setget方法
Mapper.java
package frame.domain;
public class Mapper {
private String sql;
private String resultType;
省略setget方法
}
SqlSession接口
package frame.dao;
import java.util.List;
/**
* 框架的入口,提供增删改查方法
*
*/
public interface SqlSession {
/**
* 执行查询的方法
* @param mapperId 唯一的id
* @return 返回对象的集合
*/
public List selectList(String mapperId);
/**
* 释放资源
*/
public void close();
}
接口实现
package frame.dao.impl;
import frame.dao.SqlSession;
import frame.domain.Configuration;
import frame.domain.Mapper;
import frame.utils.Executor;
import java.util.List;
/**
* SqlSession的实现类
*/
public class SqlSessionImpl implements SqlSession {
private Configuration cfg;
private Executor executor;
//外部传入Configuration对象
public SqlSessionImpl(Configuration cfg) {
this.cfg = cfg;
//为了保证两个方法使用同一执行器
executor = new Executor(cfg);
}
@Override
public List selectList(String mapperId) {
//根据mapperId获取sql和resultType
Mapper mapper = cfg.getXmlMap().get(mapperId);
String sql = mapper.getSql();
String resultType = mapper.getResultType();
return executor.executeQuery(sql ,resultType);
}
@Override
public void close() {
executor.close();
}
}
执行sql语句的工具类
package frame.utils;
import frame.domain.Configuration;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class Executor {
private Configuration cfg;
public Executor(Configuration cfg) {
this.cfg = cfg;
}
private Connection conn = null;
private PreparedStatement pst = null;
private ResultSet rs = null;
/**
* driverClass,url,username ,password
* @param sql
* @param resultType
* @return
*/
public List executeQuery(String sql , String resultType){
List list = new ArrayList();
//1. 加载驱动类
try {
Class.forName(cfg.getDriverClass());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
//2. 获取连接
conn = DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
//3. 创建Statement对象
pst = conn.prepareStatement(sql);
//4. 执行sql语句,返回结果集
rs = pst.executeQuery();
//5. 处理结果集
//5.1 通过结果集获取所有的列名
List<String> columnNames = new ArrayList<>();
//获取结果集的元数据: 修饰代码的代码 -- 注解
ResultSetMetaData metaData = rs.getMetaData();
//获取列的个数
int columnCount = metaData.getColumnCount();
//根据列的索引得到列名 metaData.getColumnName() ,列的索引从1开始
for (int i = 1; i <= columnCount ; i++) {
String columnName = metaData.getColumnName(i);
columnNames.add(columnName);
}
//通过全限类名创建对象: 获取字节码文件 -- 反射创建对象
Class clz = Class.forName(resultType);
while(rs.next()){ //rs.next() 判断是否有下一个元素,如果有,就应该有一个新的对象
//创建一个对象
Object o = clz.newInstance();
//给对象赋值
//获取某列对应set方法,执行set方法,赋值
Method[] methods = clz.getMethods();
for (Method method : methods) {
for (String columnName : columnNames) {
//使用每一个列名与每一个方法进行比较,找到某列对应set方法
//setusername 忽略大小写比较 setUsername
if(("set"+columnName).equalsIgnoreCase(method.getName())){
//method就是我需要的set方法,
//属性对应的值
Object columnValue = rs.getObject(columnName);
//执行set方法
//invoke(要执行的对象, 方法的参数)
method.invoke(o ,columnValue);
}
}
}
//添加到返回的集合中
list.add(o);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
public void close(){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(pst != null){
try {
pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
SqlSessionFactory
package frame.factory;
import frame.dao.SqlSession;
import frame.dao.impl.SqlSessionImpl;
import frame.domain.Configuration;
import frame.domain.Mapper;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/**
* 使用工厂模式创建SqlSession对象
*/
public class SqlSessionFactory {
//配置文件的输入流
private InputStream inputStream;
public SqlSessionFactory(InputStream inputStream) {
this.inputStream = inputStream;
}
private Configuration cfg ;
public SqlSession openSession(){
cfg = new Configuration();
//给cfg赋值
loadConfiguration();
//创建sqlSession对象
SqlSession sqlSession = new SqlSessionImpl(cfg);
return sqlSession;
}
private void loadConfiguration() {
//dom4j (document for java)解析时 ,直接可以解析InputStream
//创建解析对象
SAXReader reader = new SAXReader();
// 获取文档对象: 根据字节输入流获取文档对象
Document doc = null;
try {
doc = reader.read(inputStream);
// 获取根节点
Element root = doc.getRootElement();
//获取所有property节点
//selectNodes("//property"); 能得到文档所有的property, 参数必须加 //前缀
List<Element> list = root.selectNodes("//property");
for (Element element : list) {
//element.attributeValue(): 可以获取指定属性的的属性值
String value = element.attributeValue("value");
String name = element.attributeValue("name");
// 判断是哪个一个属性
if(name.equals("driver")){
cfg.setDriverClass(value);
}
if(name.equals("url")){
cfg.setUrl(value);
}
if(name.equals("username")){
cfg.setUsername(value);
}
if(name.equals("password")){
cfg.setPassword(value);
}
}
} catch (Exception e) {
e.printStackTrace();
}
//加载映射文件
//得到根节点对象
Element root = doc.getRootElement();
//得到mappers节点
//element(mappers):得到第一个名字mappers的子节点
//elements(mappers):得到所有名字mappers的子节点
//elements():得到所有子节点
Element mappers = root.element("mappers");
//获取所有的mapper子节点
List<Element> mapperList = mappers.elements("mapper");
//获取mapper的资源路径
for (Element element : mapperList) {
String path = element.attributeValue("resource");
loadXmlConfiguration(path);
}
}
/**
* 读取映射文件
*/
private void loadXmlConfiguration(String path){
//通过资源路径获取资源输入流对象
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
//dom4j解析对象
SAXReader reader = new SAXReader();
try {
//获取文档对象
Document doc = reader.read(inputStream);
//获取根节点
Element root = doc.getRootElement();
//获取根节点的属性namespace的属性值
String namespace = root.attributeValue("namespace");
//获取根节点中所有的子节点
List<Element> elements = root.elements();
//遍历每一个节点,获取 id, resultType ,sql语句
for (Element element : elements) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
//节点中的文本就是sql语句
String sql = element.getTextTrim();
// key :mapperId = namespace + id
// value :Mapper = SQL + resultType
String key = namespace +"."+ id;
Mapper value = new Mapper();
value.setSql(sql);
value.setResultType(resultType);
cfg.getXmlMap().put(key ,value);
}
} catch (DocumentException e) {
e.printStackTrace();
}
}
}
SqlSessionFactoryBuilder
package frame.factory;
import java.io.InputStream;
/**
* 使用构建者模式创建SqlSessionFactory对象
*/
public class SqlSessionFactoryBuilder {
/**
* 通过配置文件的输入流创建SqlSessionFactory对象
* @param inputStream
* @return
*/
public SqlSessionFactory build(InputStream inputStream){
return new SqlSessionFactory(inputStream);
}
/**
* 通过配置文件的输入流创建SqlSessionFactory对象
* @param path
* @return
*/
public SqlSessionFactory build(String path){
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
return new SqlSessionFactory(inputStream);
}
/**
* 没有参数创建SqlSessionFactory对象
* 假设某位置有有sqlMapConfig
* @return
*/
public SqlSessionFactory build(){
String path = "SqlMapConfig.xml";
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
return new SqlSessionFactory(inputStream);
}
}
这样就编写好了自定义的Mybatis框架,下面删除掉xml映射文件,打jar包,再写测试程序来使用我们自定义的框架。
引入依赖
<?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.itheima</groupId>
<artifactId>mybatis_day01_3_test_custom_frame</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--引入自定义框架的jar包-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>mybatis_day01_2_custom_frame</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--mysql的驱动jar包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
写SqlMapperConfig.xml和UserMapper.xml,上面有,这里省略不粘贴了。
User类
package com.itheima.domain;
/**
* 包装类型: 初始值为null
* 基本数据类型:本身的值就是一个状态
*
*/
public class User {
private Integer id;
private String username;
private String password;
private String sex;
private String address;
省略setget方法
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
测试
package com.itheima;
import frame.dao.SqlSession;
import frame.factory.SqlSessionFactory;
import frame.factory.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
public class TestCustomFrame {
@Test
public void test(){
//获取配置文件的输入流对象
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//创建SqlSessionFactory对象
SqlSessionFactory sessionFactory = sqlSessionFactoryBuilder.build(inputStream);
//获取sqlSession对象
SqlSession sqlSession = sessionFactory.openSession();
//执行sql语句
List list = sqlSession.selectList("com.itheima.dao.UserDao.findAll");
//遍历结果集
for (Object o : list) {
System.out.println(o);
}
//关闭资源
sqlSession.close();
}
}