优秀的编程知识分享平台

网站首页 > 技术文章 正文

一、手写mybatis框架

nanyue 2024-11-21 18:56:21 技术文章 2 ℃

声明:内容来源于互联网,笔者主要进行了相关整理。

一、问题分析

1.jdbc数据库访问demo

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","root","111111");

// 定义sql语句 ?表示占位符

String sql = "select * from user where username = ?";

// 预编译sql

preparedStatement = connection.prepareStatement(sql);

// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数的值

preparedStatement.setString(1,"zhangsan");

// 向数据库发送sql执行查询,查询出结果集

resultSet = preparedStatement.executeQuery();

// 遍历结果集

User user = new User();

while(resultSet.next()){

int id = resultSet.getInt("id");

String username = resultSet.getString("username");

// 封装user对象

user.setId(id);

user.setUsername(username);

}

System.out.println(user);

}catch(Exception e){

e.printStackTrace();

}finally {

// 释放连接

try {

connection.close();

} catch (SQLException e) {

e.printStackTrace();

}

}

2.JDBC访问数据问题分析:

1.数据库配置信息存在硬编码--解决方案:配置文件

2.频繁创建释放数据库连接--解决方案:连接池

3.sql语句、设置参数、获取结果集参数均存在硬编码问题--解决方案:配置文件

4.手动封装返回结果集,较为繁琐。--解决方案:反射

二、自定义持久层框架设计思想:

1、使用端(项目):引入自定义持久层框架的jar包

提供两部分配置信息:数据库配置信息,sql配置信息:sql语句,参数类型,返回值类型

使用配置文件来提供这两部分信息

1)sqlMapConfig.xml:存储数据库配置信息,存放mapper.xml的全路径

2)mapping.xml:存放sql配置信息

2、自定义持久层框架(工具):本质就是对jdbc代码进行封装

1)加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中

创建Resources类,方法InputStream getResourceAsStream(String path)

2)创建容器对象:两个JavaBean,存放的就是配置文件解析出来的容器对象

Configuration:核心配置类,存放sqlMapConfig.xml解析出来的内容

MappedStatement:映射配置类,存放mapping.xml解析出来的内容

3)解析配置配置文件:dom4j

创建类:SqlSessionFactoryBuilder 方法:build(InputStream in)

第一:使用dom4j解析配置文件,将解析出来的内容封装到容器对象中

第二:创建SqlSessionFactory对象:生产sqlSession会话对象(工厂模式)

4)创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory

第一:openSession()生产sqlSession

5)创建SqlSession接口及实现类DefaultSession

定义数据库的curd操作:selectList(),selectOne(),update(),delete()

6)创建Executor接口及实现SimpleExecutor实现类

query(Configuration,MappedStatement,Object...params)执行的就是jdbc代码

三、实现过程

1.编写sqlMapConfig.xml

用于存放数据库连接配置,并关联mapper

<configuration>

<!--数据库配置信息-->

<dataSource>

<property name="driverClass" value="com.jdbc.Driver"></property>

<!-- ///表示本地的数据 -->

<property name="jdbcUrl" value="jdbc:mysql:///mybatis"></property>

<property name="username" value="root"></property>

<property name="password" value="111111"></property>

</dataSource>

<!--存放mapper.xml的全路径-->

<mapper resource="UserMapper.xml"></mapper>

</configuration>

2.编写sql配置信息

<mapper namespace="com.mybatis.dao.IUserDao">

<!--

select表示查询

id表示sql的标识

sql的唯一标识:namespace.id来组成,statementId

resultType返回值类型

-->

<select id="findAll" resultType="com.mybatis.pojo.User">

select * from user

</select>

<!--

参数传递:

User user = new User();

user.setId(1);

user.setUsername("tom")

使用#{xx}通过反射的方式,从paramType中获取xx属性的值作为sql的参数

-->

<select id="findByCondition" resultType="com.mybatis.pojo.User" paramType="com.mybatis.pojo.User">

select * from user where id = #{id} and username = #{username}

</select>

</mapper>

3.配置文件为输入流

public class Resources {

/**

* 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中

* @param path

* @return

*/

public static InputStream getResourcesAsStream(String path){

InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);

return resourceAsStream;

}

}

4.编写配置文件解析实体

1)mappedStatement解析mapper文件内容

public class MappedStatement {

/**

* id标识

*/

private String id;

/**

* 返回值类型

*/

private String resultType;

/**

* 参数类型

*/

private String paramType;

/**

* sql语句

*/

private String sql;

2)configuration对sqlMapConfig解析

public class Configuration {

/**

* 数据库配置信息

*/

private DataSource dateSource;

/**

* key:statementId就是sql的唯一标识,namespace+sql id

* value:一个封装好sql数据

*/

private Map<String,MappedStatement> mappedStatementMap = new HashMap();

5.获取SqlSessionFactory对象

1)解析sqlMapConfig.xml配置信息封装成Configuration

public class XMLConfigBuilder {


private Configuration configuration;


public XMLConfigBuilder(){

this.configuration = new Configuration();

}


/**

* 将配置文件解析成并封装成Configuration

* @param in

* @return

*/

public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {

Document document = new SAXReader().read(in);

// 获取到跟<configuration>

Element configurationElt = document.getRootElement();

// 获取dataSource的配置,并使用连接池对象

List<Element> list = configurationElt.selectNodes("//property");

Properties properties = new Properties();

for(Element element:list){

String name = element.attributeValue("name");

String value = element.attributeValue("value");

properties.put(name,value);

}


// 封装成连接池对象

ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();

comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));

comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));

comboPooledDataSource.setUser(properties.getProperty("username"));

comboPooledDataSource.setPassword(properties.getProperty("password"));


configuration.setDateSource(comboPooledDataSource);

// mapper.xml解析:拿到路径--字节输入流--dom4j解析

List<Element> mapperList = configurationElt.selectNodes("//mapper");

for(Element element:mapperList){

String mapperPath = element.attributeValue("resource");

InputStream mapperIn = Resources.getResourcesAsStream(mapperPath);

XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);

xmlMapperBuilder.parse(mapperIn);

}

return configuration;

}

}

2)mapper配置文件信息解析

public class XMLMapperBuilder {


private Configuration configuration;


public XMLMapperBuilder(Configuration configuration){

this.configuration = configuration;

}


/**

* 解析mapper.xml

* @param in

* @throws DocumentException

*/

public void parse(InputStream in) throws DocumentException {

Document document = new SAXReader().read(in);

Element rootElt = document.getRootElement();

String namespace = rootElt.attributeValue("namespace");


/**

* <select id="selectOne" resultType="com.persistence.pojo.User" paramType="com.persistence.pojo.User">

* select * from user where id = #{id} and username = #{username}

* </select>

*/

List<Element> elementList = rootElt.selectNodes("//select");

for(Element element:elementList){

String id = element.attributeValue("id");

String resultType = element.attributeValue("resultType");

String paramType = element.attributeValue("paramType");

String sql = element.getTextTrim();

MappedStatement mappedStatement = new MappedStatement();

mappedStatement.setId(id);

mappedStatement.setResultType(resultType);

mappedStatement.setParamType(paramType);

mappedStatement.setSql(sql);

String statementId = namespace+"."+id;

configuration.getMappedStatementMap().put(statementId,mappedStatement);

}

}

}

3)sqlSession工厂

//sqlSession接口

public interface SqlSessionFactory {

}

//sqlSession默认实现

public class DefaultSessionFactory implements SqlSessionFactory {


private Configuration configuration;


public DefaultSessionFactory(Configuration configuration){

this.configuration = configuration;

}

}

4)SqlSession工厂构造流程化

// 通过sqlSessionFactoryBuilder来串联整个步骤

public class SqlSessionFactoryBuilder {


/**

* 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中

* 第二:创建sqlSessionFactory对象

* @param in

* @return

*/

public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {

//第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中

XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();

Configuration configuration = xmlConfigBuilder.parseConfig(in);

//第二:创建sqlSessionFactory对象,工厂类生产sqlSession会话对象

SqlSessionFactory sqlSessionFactory = new DefaultSessionFactory(configuration);

return sqlSessionFactory;

}

}

6.获取SqlSession,并执行sql

1)从sqlSession中获取sqlSession

// sqlSession工厂接口定义获取SqlSession的方法

public interface SqlSessionFactory {

SqlSession openSession();

}

//sqlSesssion工厂中实现获取sqlSession的方法

public class DefaultSessionFactory implements SqlSessionFactory {


private Configuration configuration;


public DefaultSessionFactory(Configuration configuration){

this.configuration = configuration;

}


@Override

public SqlSession openSession() {

return new DefaultSqlSession(configuration);

}

}

2)sqlSession中调用执行器,执行具体的方法

// sqlSession接口定义常用sql方法(curd)

public interface SqlSession {

/**

* 查询多个对象

* @param statementId

* @param param

* @param <E>

* @return

*/

<E> List<E> selectList(String statementId,Object...param);

/**

* 查询一个对象

* @param <E>

* @param statementId

* @param param

*/

<E> E selectOne(String statementId, Object...param);

}

//DefaultSqlSession实现中实现方法

public class DefaultSqlSession implements SqlSession {


private Configuration configuration;


public DefaultSqlSession(Configuration configuration) {

this.configuration = configuration;

}


@Override

public <E> List<E> selectList(String statementId, Object... param) {

// 构建sql执行器

Executor executor = new SimpleExecutor();

// 获取mapper配置中所有sql配置信息

Map<String, MappedStatement> mappedStatementMap = configuration.getMappedStatementMap();

try {

// 执行当前sql

return executor.queryList(configuration,mappedStatementMap.get(statementId),param);

} catch (SQLException e) {

e.printStackTrace();

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (NoSuchFieldException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

} catch (IntrospectionException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

e.printStackTrace();

}

return null;

}


@Override

public <E> E selectOne(String statementId, Object... param) {

List<Object> objects = selectList(statementId,param);

if(objects.size()==1){

return (E)objects.get(0);

}else{

throw new RuntimeException("查询结果为空或者返回结果为多个");

}

}

3.Executor执行器

//exector执行器接口定义

public interface Executor {

<E> List<E> queryList(Configuration configuration, MappedStatement mappedStatement,Object...params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException;

}

//执行器实现

public class SimpleExecutor implements Executor{


@Override

public <E> List<E> queryList(Configuration configuration, MappedStatement mappedStatement,Object...params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException {

//1.注册驱动,活动连接

Connection connection = configuration.getDateSource().getConnection();

//2.获取sql select * from user where id = #{id} and username = #{username}

// 转换为jdbc的 select * from user where id = ? and username = ?

String sql = mappedStatement.getSql();

BoundSql boundSql = getBoundSql(sql);

//3.预处理对象preparedStatement

PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

//4.设置参数

//获取参数类型

String paramType = mappedStatement.getParamType();

Class<?> paramTypeClass = getClassType(paramType);

List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();

for(int i=0;i<parameterMappingList.size();i++){

ParameterMapping parameterMapping = parameterMappingList.get(i);

//获取sql参数中属性值(#{}中的值)

String content = parameterMapping.getContent();

//使用反射,根据返回获取content属性对应参数中的值

Field declaredField = paramTypeClass.getDeclaredField(content);

//暴力访问

declaredField.setAccessible(true);

Object o = declaredField.get(params[0]);


preparedStatement.setObject(i+1,o);

}

//5.执行sql

ResultSet resultSet = preparedStatement.executeQuery();


//6.封装返回结果集

String resultType = mappedStatement.getResultType();

Class<?> resultTypeClass = getClassType(resultType);

List<Object> objects = new ArrayList<Object>();

while(resultSet.next()){

Object o = resultTypeClass.newInstance();

ResultSetMetaData metaData = resultSet.getMetaData();

for(int i=0;i<metaData.getColumnCount();i++){

//字段名

String columnName = metaData.getColumnName(i+1);

//字段值

Object value = resultSet.getObject(columnName);


//使用反射,根据数据库表和实体的对应关系,完成封装

PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass);

Method method = propertyDescriptor.getWriteMethod();

method.invoke(o,value);

}

objects.add(o);

}

return (List<E>)objects;

}


private Class<?> getClassType(String paramType) throws ClassNotFoundException {

if(paramType!=null){

Class<?> aClass = Class.forName(paramType);

return aClass;

}

return null;

}


/**

* 完成对#{}的解析工作,1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储

* @param sql

* @return

*/

private BoundSql getBoundSql(String sql){

// 标记处理类:配置标记解析器完成对占位符的解析处理工作

ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();

GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",parameterMappingTokenHandler);

//解析过后的sql

String parseSql = genericTokenParser.parse(sql);

//#{}解析出来的参数名称

List<ParameterMapping> parameterMappingList = parameterMappingTokenHandler.getParameterMappingList();

BoundSql boundSql = new BoundSql(parseSql,parameterMappingList);

return boundSql;

}

}

4)sql占位符分析

//参数中属性值#{}中的具体值

public class ParameterMapping {

private String content;


public ParameterMapping(String content) {

this.content = content;

}


public String getContent() {

return content;

}

}

//占位符解析器定义

public interface TokenHandler {

String handleToken(String content);

}


public class ParameterMappingTokenHandler implements TokenHandler{


private List<ParameterMapping> parameterMappingList = new ArrayList();


@Override

public String handleToken(String content) {

parameterMappingList.add(buildParameterMapping(content));

return "?";

}


private ParameterMapping buildParameterMapping(String content) {

ParameterMapping parameterMapping = new ParameterMapping(content);

return parameterMapping;

}


public List<ParameterMapping> getParameterMappingList() {

return parameterMappingList;

}

}

public class GenericTokenParser{

/**

* 开始标记

*/

private String openToken;

/**

* 结束标记

*/

private String closeToken;

/**

* 标记处理器

*/

private TokenHandler handler;


public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {

this.openToken = openToken;

this.closeToken = closeToken;

this.handler = handler;

}


/**

* 解析#{}和${}

* @param text

* @return

* 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。

* 其中,解析工作由该方法完成,处理工作是由处理器handler的handlerToken()方法完成

*/

public String parse(String text) {

// 验证参数问题,如果是null,就返回空字符串

if(text==null||text.isEmpty()){

return "";

}

// 下面继续验证是否包含开始标记和结束标记,默认不是占位符,直接原样返回即可,否则继续执行

int start = text.indexOf(openToken,0);

if(start==-1){

return text;

}

//把text转成字符串数组src,并且定义默认偏移量offset=0,存储最终需要返回的字符串的变量builder

//text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在则执行如下代码

char[] src = text.toCharArray();

int offset = 0;

final StringBuilder builder = new StringBuilder();

StringBuilder expression = null;

while(start>-1){

//判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理

if(start>0&&src[start-1]=='\\'){

builder.append(src,offset,start-offset-1).append(openToken);

offset = start+openToken.length();

}else{

//重置expression变量,避免空指针或者老数据干扰

if(expression==null){

expression = new StringBuilder();

}else{

expression.setLength(0);

}

builder.append(src,offset,start-offset);

offset = start+openToken.length();

int end = text.indexOf(closeToken,offset);

while(end>-1){

//存在结束标记

if(end>offset&&src[end-1]=='\\'){

expression.append(src,offset,end-offset-1).append(closeToken);

}else{

expression.append(src,offset,end-offset);

offset=end+closeToken.length();

break;

}

}

if(end==-1){

builder.append(src,start,src.length-start);

offset=src.length;

}else{

builder.append(handler.handleToken(expression.toString()));

offset=end+closeToken.length();

}

}

start=text.indexOf(openToken,offset);

}

return builder.toString();

}

}

public class BoundSql {

/**

* 解析后的sql

*/

private String sqlText;


/**

* 参数

*/

private List<ParameterMapping> parameterMappingList;


public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {

this.sqlText = sqlText;

this.parameterMappingList = parameterMappingList;

}


public String getSqlText() {

return sqlText;

}


public List<ParameterMapping> getParameterMappingList() {

return parameterMappingList;

}

}

5)测试流程

@Test

public void test() throws Exception {

InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);

SqlSession sqlSession = sessionFactory.openSession();

// 流程测试

User user = new User();

user.setId(1);

user.setUsername("zhangsan");

User user0 = sqlSession.selectOne("com.mybatis.dao.IUserDao.findByCondition",user);

System.out.println(user0);

}

6)编写dao和dao实现

public interface IUserDao {


List<User> findAll() throws Exception;


User findByCondition(User user) throws Exception;

}

public class UserDaoImpl implements IUserDao{


public List<User> findAll() throws Exception{

InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);

SqlSession sqlSession = sessionFactory.openSession();

// 调用

List<User> userList = sqlSession.selectList("com.mybatis.dao.IUserDao.findAll",null);

for(User user:userList){

System.out.println(user);

}

return userList;

}


public User findByCondition(User user) throws Exception{

InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);

SqlSession sqlSession = sessionFactory.openSession();

// 调用

User user2 = sqlSession.selectOne("com.mybatis.dao.IUserDao.findByCondition",user);

System.out.println(user2);

return user2;

}

}

测试流程

@Test

public void test() throws Exception {

InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);

SqlSession sqlSession = sessionFactory.openSession();

// 流程测试

User user = new User();

user.setId(1);

user.setUsername("zhangsan");

User user0 = sqlSession.selectOne("com.mybatis.dao.IUserDao.findByCondition",user);

System.out.println(user0);


// 传统方式调用(需要在dao每个方法中获取sqlSession,重复代码比较多)

IUserDao iUserDao = new UserDaoImpl();

User user1 = iUserDao.findByCondition(user);

System.out.println(user1);

}

此时发现,自定义持久层框架问题分析:

1.dao层使用自定义持久层框架,存在代码重复,整个操作的过程模板重复(加载配置文件,创建sqlSessionFactory,sqlSession).

2.statementId存在硬编码

解决方案:

使用代理模式生成dao层接口代理实现类

7)编写代理模式getMapper

public interface SqlSession {

/**

* 为dao接口生成代理实现类

* @param <T>

* @return

*/

<T> T getMappper(Class<?> mapperClass);

}

@Override

public <T> T getMappper(Class<?> mapperClass) {

// 使用jdk动态代理来为dao接口生成代理对象,并返回。

Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// proxy:当前代理对象的应用

// method:当前被调用方法的引用

// args:传递的参数

//底层还是去执行jdbc代码,根据不同情况,来执行selectOne或者selectList

//准备参数:

//参数1:statementId:sql语句的唯一表示,由mapper文件的namespace.id组成。

// 此时无法获取到,但是通常会按照一定规范来操作,即namespace值使用dao的全限定名,id使用方法名

String methodName = method.getName();

String className = method.getDeclaringClass().getName();

String statementId = className+"."+methodName;

//参数2:就是args


// 获取调用方法执行调用

Type genericReturnType = method.getGenericReturnType();

// 判断是否进行了泛型类型参数化

if(genericReturnType instanceof ParameterizedType){

List<Object> objects = selectList(statementId,args);

return objects;

}else{

return selectOne(statementId,args);

}

}

});

return (T)proxyInstance;

}

测试流程:

@Test

public void test() throws Exception {

InputStream in = Resources.getResourcesAsStream("sqlMapConfig.xml");

SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(in);

SqlSession sqlSession = sessionFactory.openSession();

// 流程测试

User user = new User();

user.setId(1);

user.setUsername("zhangsan");

User user0 = sqlSession.selectOne("com.mybatis.dao.IUserDao.findByCondition",user);

System.out.println(user0);


// 传统方式调用(需要在dao每个方法中获取sqlSession,重复代码比较多)

IUserDao iUserDao = new UserDaoImpl();

User user1 = iUserDao.findByCondition(user);

System.out.println(user1);


// 使用代理对象来调用方法(统一在外层处理一次)

IUserDao userDao = sqlSession.getMappper(IUserDao.class);

List<User> userList = userDao.findAll();

for (User user2:userList){

System.out.println(user2);

}

}

Tags:

最近发表
标签列表