Mybatis框架简单源码实现
Mybatis框架简单源码实现
创建一个mybatis框架,我们首先要做以下几件事
1.创建配置信息对应的实体类Configuration和MapperStatement,将配置信息提取出来时进行封装
2.创建SqlSessionFactory工厂类,加载配置信息
3.通过SqlSessionFactory对象创建SqlSession
4.通过SqlSession获取mapper接口的动态代理
5.动态代理回调SqlSession中的查询方法
6.SqlSession将查询方法传递给Executor执行器
7.执行器获取数据库连接
8.执行器通过反射将执行语句,返回geiSqlSession
9.将数据返回给调用者
首先我们封装一个加载xml的实体类
package com.ys.config;
/**
* 解析xml配置文件中的标签含义
* @author ys
*/
public class MappedStatement {
//namespace标签
private String namespace;
//SQL语句标签的id属性
private String sourceId;
//返回值类型标签
private String resultType;
//存储sql语句
private String sql;
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getSourceId() {
return sourceId;
}
public void setSourceId(String sourceId) {
this.sourceId = sourceId;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
}
创建一个封装properties文件的实体类
在这个类中,xml配置文件的信息存储在此类的map集合中
包名+方法名作为key值
封装xml类的对象作为value值
package com.ys.config;
import java.util.HashMap;
import java.util.Map;
/**
* 这是一个更大的存储配置信息的文件
*
* 加载properties文件
* @author ys
*/
public class Configuration {
//加载properties配置文件中的连接数据库的信息
private String jdbcDriver;
private String jdbcUrl;
private String jdbcUsername;
private String jdbcPassword;
//存储多个xml文件
private Map<String,MappedStatement> mappedStatement = new HashMap<String, MappedStatement>();
public String getJdbcDriver() {
return jdbcDriver;
}
public void setJdbcDriver(String jdbcDriver) {
this.jdbcDriver = jdbcDriver;
}
public String getJdbcUrl() {
return jdbcUrl;
}
public void setJdbcUrl(String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
}
public String getJdbcUsername() {
return jdbcUsername;
}
public void setJdbcUsername(String jdbcUsername) {
this.jdbcUsername = jdbcUsername;
}
public String getJdbcPassword() {
return jdbcPassword;
}
public void setJdbcPassword(String jdbcPassword) {
this.jdbcPassword = jdbcPassword;
}
public Map<String, MappedStatement> getMappedStatement() {
return mappedStatement;
}
public void setMappedStatement(Map<String, MappedStatement> mappedStatement) {
this.mappedStatement = mappedStatement;
}
}
sqlsessionfactory类(两个功能,1.加载配置信息,生成sqlsession)
package com.ys.session;
/**
*1.在初始化是加载配置信息到configuration
*2.生成sqlSession
* @author ys
*/
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Properties;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.ys.config.Configuration;
import com.ys.config.MappedStatement;
public class SqlSessionFactory {
//全局唯一的对象
private final Configuration conf = new Configuration();
public SqlSessionFactory(){
//实例化时加载db.properties的数据库配置信息
loadDbInfo();
//将多个xml配置文件加载到Configuration对象中
loadMappersInfo();
}
//记录mapper.xml文件存储位置
public static final String MAPPER_CONFIG_LOCATION = "mappers";
//记录连接数据库连接信息文件的存储位置
public static final String DB_CONFIG_FILE = "db.properties";
//加载数据库配置信息
private void loadMappersInfo() {
//加载数据库信息配置文件
InputStream dbIn = SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
//持久化存储集合
Properties p = new Properties();
try{
//读取配置文件中的信息.写入Properties对象
p.load(dbIn);
}catch (Exception e) {
e.printStackTrace();
}
/*
* jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.username=root
jdbc.password=root
*/
//将数据库配置信息写入conf对象
conf.setJdbcDriver(p.get("jdbc.driver").toString());
conf.setJdbcUrl(p.get("jdbc.url").toString());
conf.setJdbcUsername(p.get("jdbc.username").toString());
conf.setJdbcPassword(p.get("jdbc.password").toString());
}
//加载指定文件夹下的所有.xml文件
private void loadDbInfo() {
//创建mapper.xml的存储文件夹
URL resources = null;
//赋予路径
resources = SqlSessionFactory.class.getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
//获取指定文件夹信息
File mappers = new File(resources.getFile());
//遍历文件
if(mappers.isDirectory()){
//将该文件夹下所有目录和文件存储
File[] listFiles = mappers.listFiles();
//遍历文件夹下所有mapper.xml,解析信息后,注册至conf
for (File file : listFiles) {
//判断是否是xml文件
// if(file.getName().endsWith(".xml")){
loadMapperInfo(file);
// }
}
}
}
//加载指定的
private void loadMapperInfo(File file) {
//创建saxReader对象
SAXReader reader = new SAXReader();
//通过read方法读取一个文件转换成Document对象
Document document = null;
try{
document = reader.read(file);
}catch (Exception e) {
e.printStackTrace();
}
//获取节点元素mapper/根节点
Element root = document.getRootElement();
//获取命名空间
String namespace = root.attribute("namespace").getData().toString();
//获取select子节点列表
List<Element> selects = root.elements("select");
//遍历select节点,将信息记录到MappedStatement对象,并记录到Configuration对象中的map中
for (Element element : selects) {
//实例化MappedStatement
MappedStatement mappedStatement = new MappedStatement();
//选取id属性
String id = element.attribute("id").getData().toString();
//获取resultType属性
String resultType = element.attribute("resultType").getData().toString();
//读取sql语句信息
String sql = element.getData().toString();
//对 namespace和id属性进行拼接
String sourceId = namespace+"."+id;
//将信息封装到mappedStatement对象中
mappedStatement.setSourceId(sourceId);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(sql);
mappedStatement.setNamespace(namespace);
//注册到Configuration实例化对象map集合中
conf.getMappedStatement().put(sourceId, mappedStatement);
}
}
//第二功能 生成sqlsession
public SqlSession openSession(){
return new DefaultSqlSession(conf);
}
}
sqlsession接口类
package com.ys.session;
import java.util.List;
/**
* mybatis暴露给外部的接口,实现增删改查的能力
* @author ys
*
*1.对外提供数据访问的API
*2.对内将请求转发给executor
*/
public interface SqlSession {
/**
* 根据传入的条件查询单一结果
*
* @param statement 方法对应的sql语句
* @param parameter 要传入到SQL语句中的查询参数
* @return 返回指定的结果对象
*/
<T> T selectOne(String statement,Object parameter);
/**
* 根据条件经过查询,返回泛型集合
* @param statement 方法对应的sql语句
* @param parameter 要传入到SQL语句中的查询参数
* @return 返回指定的结果对象
*/
<E> List<E> selectList(String statement,Object parameter);
/**
* 根据mapper接口获取接口对应的动态代理实现
* @param type 指定的mapper接口
* @return
*/
<T> T getMapper(Class<T> type);
}
sqlsession实现类
1.提供对外的实现方法,(直接去封装好的configuration对象中的map集合中获取对应的sql语句)
2.获取执行器
3.为接口实现动态代理对象
package com.ys.session;
import java.lang.reflect.Proxy;
/**
* mybatis暴露给外部的接口,实现增删该查的能力
* @author ys
*
* 1.对外提供数据访问的API
* 2.对内将请求转发给executor
*/
import java.util.List;
import com.ys.Executor.DefaultExecutor;
import com.ys.Executor.Executor;
import com.ys.binding.MapperProxy;
import com.ys.config.Configuration;
import com.ys.config.MappedStatement;
public class DefaultSqlSession implements SqlSession{
//获取容器(加了final字段内存地址不会改变,不可修改,这样就可以提升效率)
private final Configuration conf;
//获取执行器
private Executor executor;
public DefaultSqlSession(Configuration conf){
super();
this.conf = conf;
//加载执行器
executor = new DefaultExecutor(conf);
}
public <T> T selectOne(String statement, Object parameter) {
List<Object> selectList = this.selectList(statement, parameter);
if(selectList == null || selectList.size() == 0){
return null;
}
if(selectList.size() == 1){
//类型强转
return (T) selectList.get(0);
}else{
throw new RuntimeException("Too Many Results!");
}
}
public <E> List<E> selectList(String statement, Object parameter) {
//获取方法对应的SQL语句
MappedStatement ms = conf.getMappedStatement().get(statement);
return executor.query(ms, parameter);
}
//等待传入接口类型
public <T> T getMapper(Class<T> type) {
MapperProxy mp = new MapperProxy(this);
//生成动态代理对象
return (T) Proxy.newProxyInstance(
type.getClassLoader(),
new Class[]{type},
mp);
}
}
动态代理业务实现
1.通过方法名获取对象名之后拼接上方法名,就可以找到map中的key值了
2.判断sql语句是否需要传入参数
package com.ys.binding;
/**
* 动态代理业务逻辑实现
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;
import com.ys.session.SqlSession;
public class MapperProxy implements InvocationHandler {
private SqlSession session;
public MapperProxy(SqlSession session) {
super();
this.session = session;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取调用方法的返回类型
Class<?> returnType = method.getReturnType();
//如果返回值是集合类
if(Collection.class.isAssignableFrom(returnType)){
//第一个参数:获取类名全写+方法名(即Configuration对象中的map的key值)
//第二个参数:简写,应该写成集合返回,这里只写了第一个测试
return session.selectList(method.getDeclaringClass().getName()+"."+method.getName(),
args == null?null:args[0]);
}else{
return session.selectOne(method.getDeclaringClass().getName()+"."+method.getName(),
args == null?null:args[0]);
}
}
}
执行器接口
package com.ys.Executor;
import java.util.List;
import com.ys.config.MappedStatement;
/**
* Mybatis的核心接口之一,定义了数据库的最基本的方法,sqlsession的功能都是基于它实现的
* @author TEDU
*
*/
public interface Executor {
/**
* 查询接口
* @param ms 封装sql语句的MappedStatement参数
* @param parameter 传入sql的参数
* @return 将数据转换成指定对象的结果集返回
*/
<E> List<E> query(MappedStatement ms ,Object parameter);
}
执行器实现类
1.首先获取数据库的连接
2.对perpreparedStatement占位符进行处理
3.将查询到的数据封装到返回值类型中
package com.ys.Executor;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.ys.config.Configuration;
import com.ys.config.MappedStatement;
import com.ys.util.ReflectionUtil;
public class DefaultExecutor implements Executor{
private final Configuration conf;
public DefaultExecutor(Configuration conf) {
super();
this.conf = conf;
}
/**
* @param ms 封装sql语句MappsedStatement对象
* @param parameter 传入的sql参数
* @return 将数据转换成指定的对象结果集返回
*/
//实现JDBC规范,对数据库进行连接
public <E> List<E> query(MappedStatement ms, Object parameter) {
// System.out.println(ms.getSql());
// System.out.println(ms.getResultType());
//定义返回结果集
List<E> ret = new ArrayList<E>();
try{
//加载数据驱动
Class.forName(conf.getJdbcDriver());
}catch (Exception e) {
e.printStackTrace();
}
//获取连接
Connection connection = null;
//执行数据库语句
PreparedStatement preparedStatement = null;
//返回结果集
ResultSet resultSet = null;
try{
//获取连接,从MappedStatement对象中获取数据库信息
connection = DriverManager.getConnection(conf.getJdbcUrl(),conf.getJdbcUsername(),conf.getJdbcPassword());
//创建preparedStatement,从MappedStatement中获取sql语句
preparedStatement = connection.prepareStatement(ms.getSql());
//处理sql语句中的占位符
parameterized(preparedStatement,parameter);
//执行查询结果获取resultSet
resultSet = preparedStatement.executeQuery();
//将结果集通过反射技术,填充到list集合中
handlerResultSet(resultSet,ret,ms.getResultType());
}catch (Exception e) {
e.printStackTrace();
}finally{
try{
resultSet.close();
preparedStatement.close();
connection.close();
}catch (Exception e) {
e.printStackTrace();
}
}
return ret;
}
//读取result中的数据,并转换成目标对象
private <E> void handlerResultSet(ResultSet resultSet, List<E> ret, String className) {
Class<E> clazz = null;
try{
//通过反射获取类对象
clazz = (Class<E>) Class.forName(className);
}catch (Exception e) {
e.printStackTrace();
}
try{
while(resultSet.next()){
//通过反射实例化对象
Object entity = clazz.newInstance();
//使用反射工具类将resultSet中的数据填充到entity中
ReflectionUtil.setPropToBeanFromResultSet(entity, resultSet);
//对象加入返回集合中
ret.add((E) entity);
}
}catch (Exception e) {
e.printStackTrace();
}
}
//对perpreparedStatement占位符进行处理
private void parameterized(PreparedStatement preparedStatement, Object parameter) throws SQLException {
if(parameter instanceof Integer){
preparedStatement.setInt(1, (Integer) parameter);
}else if(parameter instanceof Long){
preparedStatement.setLong(1, (Long) parameter);
}else if(parameter instanceof String){
preparedStatement.setString(1, (String) parameter);
}
}
}
反射工具类
帮助执行器向返回值对象中添加数据
package com.ys.util;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.ys.entity.TUser;
/**
* 反射工具类
* @author ys
*
*/
public class ReflectionUtil {
/**
* 为指定的beanpropName属性的值设置为value
*
* @param bean 目标对象
* @param proName对象的属性名
* @param value 值
*/
public static void setPropToBean(Object bean ,String proName,Object value){
Field f;
try{
//获取对象指定的属性
f = bean.getClass().getDeclaredField(proName);
//将字段设置为可通过反射访问
f.setAccessible(true);
//为属性赋值
f.set(bean, value);
}catch (Exception e) {
e.printStackTrace();
}
}
/**
* 从resultSet中读取一行数据,并填充到指定的实体bean
* @param entity 待填充的实体bean
* @param resultSet 从数据库中加载的数据
* @throws SQLException
*/
public static void setPropToBeanFromResultSet(Object entity,ResultSet resultSet) throws SQLException{
//通过反射获取对象的所有字段
Field[] declaredFields = entity.getClass().getDeclaredFields();
//遍历所有的字段,从resultSet中读取相应的电话机,并填充至对象的属性中
for (int i = 0; i < declaredFields.length; i++) {
//如果是字符串类型的数据
if(declaredFields[i].getType().getSimpleName().equals("String")){
setPropToBean(entity, declaredFields[i].getName(), resultSet.getString(declaredFields[i].getName()));
//如果是int类型
}else if(declaredFields[i].getType().getSimpleName().equals("Integer")){
setPropToBean(entity, declaredFields[i].getName(), resultSet.getInt(declaredFields[i].getName()));
//如果是long类型数据
}else if(declaredFields[i].getType().getSimpleName().equals("Long")){
setPropToBean(entity, declaredFields[i].getName(), resultSet.getLong(declaredFields[i].getName()));
}
}
}
//测试
public static void main(String[] args) {
TUser user = new TUser();
ReflectionUtil.setPropToBean(user, "userName", "ys");
System.out.println(user.getUserName());
}
}
数据回调geisqlsession,这时就可以在sqlsession对象中获取查询出来的结果了
数据库实体类
package com.ys.entity;
public class TUser {
private Integer id;
private String userName;
private String realName;
private String sex;
private String mobile;
private String email;
private String note;
private Integer position_id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getRealName() {
return realName;
}
public void setRealName(String realName) {
this.realName = realName;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getmobile() {
return mobile;
}
public void setmobile(String mobile) {
this.mobile = mobile;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public Integer getPosition_id() {
return position_id;
}
public void setPosition_id(Integer position_id) {
this.position_id = position_id;
}
@Override
public String toString() {
return "TUser [id=" + id + ", userName=" + userName + ", realName=" + realName + ", sex=" + sex + ", mobile="
+ mobile + ", email=" + email + ", note=" + note + ", position_id=" + position_id + "]";
}
}
properties配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.username=root
jdbc.password=root
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.ys.mapper.TUserMapper">
<select id="selectByPrimaryKey" resultType="com.ys.entity.TUser">
select
id ,userName, realName, sex, mobile, email,note,position_id
from t_user where id = ?
</select>
<select id="selectAll" resultType="com.ys.entity.TUser">
select
id ,userName, realName, sex, mobile, email,note,position_id
from t_user
</select>
</mapper>
mapper接口
package com.ys.mapper;
import java.util.List;
import com.ys.entity.TUser;
public interface TUserMapper {
TUser selectByPrimaryKey(Integer id);
List<TUser> selectAll();
}
数据库结构
测试类
package com.ys;
import java.util.List;
import com.ys.entity.TUser;
import com.ys.mapper.TUserMapper;
import com.ys.session.SqlSession;
import com.ys.session.SqlSessionFactory;
public class TestMybatis {
public static void main(String[] args) {
//1.实例化sqlsessionfactory,加载数据配置文件及mapper.xml文件到configuration对象中
SqlSessionFactory factory = new SqlSessionFactory();
//2.获取sqlsession对象
SqlSession session = factory.openSession();
//3.通过动态代理跨域面向接口编程和ibatis编程模型的鸿沟
TUserMapper userMapper = session.getMapper(TUserMapper.class);
//4.遵循jdbc规范,通过底层的四大对象的合作完成数据查询和数据转化
TUser user = userMapper.selectByPrimaryKey(2);
System.out.println(user);
System.out.println(
"-----------------------------------------------"
+ "---------------------------------------------"
+ "------------------");
List<TUser> selectAll = userMapper.selectAll();
if(selectAll != null && selectAll.size() > 0){
for (TUser tuser : selectAll) {
System.out.println(tuser);
}
}
}
}