一、框架介绍
1.目录结构
2.执行流程
3.框架分析
代码参照使用mybatis的最基本流程进行编写,即如下步骤
//1.读取配置文件
InputStream is=Resources.getResourceAsStream("mybatis.xml");
//2.构建SqlSessionFactory
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(is);
//3.获取SqlSessoin对象
SqlSession session = factory.openSession();
//4.创建Mapper接口的代理对象
AccountMapper userMapper=session.getMapper(AccountMapper.class);
//5.执行查询
Object res= accountMapper.queryAccountById(1)
bean
- Configuartion——封装数据库连接信息和SQL语句信息
- Environment ——封装数据库连接信息
- MapperStatement ——SQL语句信息
core
- Executor——执行对数据库的操作
- MapperProxy——Mapper接口的代理类,调用SqlSession中的CRUD方法
- SqlSession——提供创建代理类的方法,通过调用Executor类来提供CRUD方法
- SqlSessionFactory——读取Configuartion类信息创建Executor类,并创建SqlSession类,将Executor类传入其中
- SqlSessionFactoryBuilder——调用XmlConfiguration类解析传入的输入流,生成Configuration类,创建SqlSessionFactory对象并将Configuration类传入
pool
- MyDataSource——数据库连接池接口类
- MyDataSource——数据库连接池实现类,双重检测锁实现单例
utils
- ReflectUtils——反射实现实体类的set方法,对结果集数据进行封装
- Resources——读取配置文件
- StringUtils——下划线和驼峰命名之间的转换
- XmlConfigBuilder——解析配置文件
整体分析
整个程序实现了简单解析mapper.xml中的sql语句(即只是获取了了sql标签中的内容,无法对如< where >等标签进行解析并对标签内容进行处理),使用构建者模式构建SqlSessionFactory并生产Session对象,在Session对象中使用动态代理创建接口的代理实现类,代理的实现类拦截接口方法并调用Session中的查询方法,由Executor类真正进行数据库操作和封装结果集对象。
GitHub地址:点此查看源码
不足之处
目前只能进行查询操作,在sql语句中只能使用?作为占位符,无法解析#{id}和${id}的占位符类型。没有实现实体类和数据库表的关系映射,没有进行事务管理,对xml文件的解析也只是对几个标签进行了解析,满足最基本配置信息的需求。
二、详细分析
1.bean对象分析
封装数据库连接信息和SQL语句信息的对象,以下只展示对象中的属性代码
1.1 Configuartion
/**
* 封装配置文件和mapper文件的信息
* @author xxbb
*/
public class Configuration {
/**
* 封装xml配置文件中的数据库连接信息
*/
private Environment environment;
/**
* 封装mapper文件的信息
*/
private Map<String,MapperStatement> mapperStatementMap;
}
1.2 Environment
/**
* 数据库连接信息
* @author xxbb
*/
public class Environment {
private String url;
private String driver;
private String username;
private String password;
}
1.3 MapperStatement
/**
* 映射mapper.xml,对其中数据进行封装
* @author xxbb
*/
public class MapperStatement {
/**
* 命名空间
*/
private String namespace;
/**
* sql语句的标签id
*/
private String id;
/**
* 传入参数类型
*/
private String parameterType;
/**
* 结果集封装参数类型
*/
private String resultType;
/**
* 执行的sql语句
*/
private String sql;
}
2.core核心业务分析
2.1 Executor
执行器,真正操作数据库的对象,其中初始化了连接池,提供对数据库操作的方法。对于查询的结果集对象,这里使用获取结果集元数据的方式得到其字段名,通过反射调用字段名对应的set方法实现将数据封装到实体类中。
/**
* 执行器,真正进行数据库操作的对象
*
* @author xxbb
*/
public class Executor {
private final DataSource dataSource;
public Executor(Configuration configuration) {
this.dataSource = MyDataSourceImpl.getInstance(configuration.getEnvironment());
}
/**
* 查询信息
* @param mapperStatement 封装了sql相关信息的对象
* @param args 需要传入sql语句中的参数
* @param <T> 泛型
* @return 泛型集合
*/
public <T> List<T> query(MapperStatement mapperStatement, Object[] args) {
Connection connection = null;
try {
connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(mapperStatement.getSql());
ResultSet resultSet;
if (args != null) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Integer) {
preparedStatement.setInt(i + 1, (Integer) args[i]);
} else if (args[i] instanceof Long) {
preparedStatement.setLong(i + 1, (Long) args[i]);
} else if (args[i] instanceof Double) {
preparedStatement.setDouble(i + 1, (Double) args[i]);
} else if (args[i] instanceof String) {
preparedStatement.setString(i + 1, (String) args[i]);
}
}
}
System.out.println("执行的查询语句:" + preparedStatement.toString());
resultSet = preparedStatement.executeQuery();
//将查询结果转化为对象
List<T> resultList = new ArrayList<>();
handlerResultType(resultSet, mapperStatement.getResultType(), resultList);
return resultList;
} catch (SQLException throwables) {
throw new RuntimeException(throwables.getMessage());
} finally {
((MyDataSourceImpl) dataSource).returnConnection(connection);
}
}
/**
* 将结果集封装到对象中
* @param resultSet 结果集
* @param resultType 封装的对象类型
* @param resultList 封装处理得到对象的集合
* @param <T> 泛型
*/
private <T> void handlerResultType(ResultSet resultSet, String resultType, List<T> resultList) {
//获取返回类型的对象
try {
Class<?> clazz = Class.forName(resultType);
//获取结果集元数据
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
//遍历结果集,封装成bean对象存入resultList中
while (resultSet.next()) {
Object entity = clazz.newInstance();
for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
//获取列名和值,从1开始,如果查询中该字段有别名则获取别名
String columnName = resultSetMetaData.getColumnLabel(i + 1);
Object columnValue = resultSet.getObject(i + 1);
//将字段值赋值给类对象
ReflectUtils.invokeSet(entity, columnName, columnValue);
}
resultList.add((T) entity);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2 MapperProxy
基于接口的动态代理,生成mapper接口的代理对象,拦截接口方法,通过接口名+方法名获取对应的存储SQL信息的MapperStatement对象,调用SqlSession中的CRUD方法。
/**
* mapper接口类的代理实现类
* @author xxbb
*/
public class MapperProxy implements InvocationHandler {
private SqlSession sqlSession;
public MapperProxy(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断返回值类型
Class<?> clazz = method.getReturnType();
if (Collection.class.isAssignableFrom(clazz)) {
String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
return sqlSession.selectList(statementKey, args);
//返回值为集合类型
} else if (Map.class.isAssignableFrom(clazz)) {
//返回值为Map类型
return null;
} else {
//返回值为单条数据
String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
return sqlSession.selectOne(statementKey, args);
}
}
}
2.3 SqlSession
提供接口的代理对象和CRUD方法,通过调用Executor类实现对数据库的操作。
/**
* @author xxbb
*/
@SuppressWarnings("unchecked")
public class SqlSession {
/**
* 配置对象
*/
private Configuration configuration;
/**
* 执行器对象
*/
private Executor executor;
public SqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
/**
* 创捷接口的代理实现类
* @param clazz 传入的接口字节码文件
* @param <T> 泛型
* @return 接口的代理实现类
*/
public <T> T getMapper(Class<T> clazz) {
MapperProxy mapperProxy = new MapperProxy(this);
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, mapperProxy);
}
public <T> T selectOne(String statementKey, Object[] args) {
//获取查询对象
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementKey);
List<T> resultList = executor.query(mapperStatement, args);
if (resultList.size() > 1) {
throw new RuntimeException("get result number > 1");
} else {
return resultList.get(0);
}
}
public <T> List<T> selectList(String statementKey, Object[] args) {
//获取查询对象
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementKey);
return executor.query(mapperStatement, args);
}
public <T> T selectMap(Object[] args) {
return null;
}
}
2.4 SqlSessionFactory
提供Executor类对象,创建SqlSession对象并将Executor类对象传入
/**
* @author xxbb
*/
public class SqlSessionFactory {
/**
* 封装xml配置信息的类
*/
private Configuration configuration;
public SqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
/**
* 创建SqlSession对象
* @return SqlSession对象
*/
public SqlSession openSession() {
Executor executor = new Executor(configuration);
return new SqlSession(configuration, executor);
}
}
2.5 SqlSessionFactoryBuilder
调用XmlConfigBuilder类对输入流进行解析,生成Configuration对象,创建SqlSessionFactory并将Configuration对象传入。
/**
* @author xxbb
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream is){
Configuration configuration=new XmlConfigBuilder().parse(is);
return new SqlSessionFactory(configuration);
}
}
3.pool连接池
简单的数据库连接池,通过双重检测锁保证单例,归还连接和获取连接之间通过notify()和wait()进行通信。在接口类中使用Default重载DataSource的方法,从而在连接池实现类中不需要去实现哪些不必要的方法。
3.1MyDataSource
/**
* 数据库连接池接口
* @author xxbb
*/
public interface MyDataSource extends DataSource {
@Override
default Connection getConnection() throws SQLException {
return null;
}
@Override
default Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
default <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
default boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
default PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
default void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
default void setLoginTimeout(int seconds) throws SQLException {
}
@Override
default int getLoginTimeout() throws SQLException {
return 0;
}
@Override
default Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
3.2 MyDataSourceImpl
/**
* 连接池实现类
* @author xxbb
*/
public class MyDataSourceImpl implements MyDataSource {
/**
* 数据库连接属性
*/
private Environment environment;
/**
* 初始连接数量
*/
private static int initCount = 5;
/**
* 最小连接数量
*/
private static int minCount = 5;
/**
* 最大连接数量
*/
private static int maxCount = 20;
/**
* 已创建的连接数量
*/
private static int createdCount;
/**
* 连接数增长步长
*/
private static int increasingCount = 2;
/**
* 存储连接的集合
*/
LinkedList<Connection> conns = new LinkedList<>();
/**
* 用于获取连接和归还连接的同步锁对象
*/
private static final Object monitor = new Object();
/**
* 连接池对象
*/
private static volatile MyDataSourceImpl instance;
private MyDataSourceImpl(Environment environment) {
//防止反射破坏单例
if (instance != null) {
throw new RuntimeException("Object has been instanced!!!");
}
//获取配置信息
this.environment = environment;
//初始化连接池
init();
}
public static MyDataSourceImpl getInstance(Environment environment) {
//双重检测锁
if (null == instance) {
synchronized (MyDataSourceImpl.class) {
if (null == instance) {
instance = new MyDataSourceImpl(environment);
}
}
}
return instance;
}
/**
* 初始化连接池
*/
private void init() {
//循环给集合中添加初始化连接
for (int i = 0; i < initCount; i++) {
boolean flag = conns.add(createConnection());
if (flag) {
createdCount++;
}
}
System.out.println("ConnectionPool1初始化------>连接池对象:" + this);
System.out.println("ConnectionPool1初始化------>连接池可用连接数量:" + createdCount);
}
/**
* 构建数据库连接对象
*
* @return
*/
private Connection createConnection() {
try {
Class.forName(environment.getDriver());
return DriverManager.getConnection(environment.getUrl(), environment.getUsername(), environment.getPassword());
} catch (Exception e) {
throw new RuntimeException("数据库连接创建失败:" + e.getMessage());
}
}
/**
* 连接自动增长
*/
private synchronized void autoAdd() {
//增长步长默认为2
if (createdCount == maxCount) {
throw new RuntimeException("连接池中连接已达最大数量,无法再次创建连接");
}
//临界时判断增长个数
for (int i = 0; i < increasingCount; i++) {
if (createdCount == maxCount) {
break;
}
conns.add(createConnection());
createdCount++;
}
}
/**
* 自动减少连接
*/
private synchronized void autoReduce() {
if (createdCount > minCount && conns.size() > 0) {
//关闭池中空闲连接
try {
conns.removeFirst().close();
createdCount--;
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 获取池中连接
*
* @return 连接
*/
@Override
public Connection getConnection() {
//判断池中是否还有连接
synchronized (monitor) {
if (conns.size() > 0) {
System.out.println(Thread.currentThread().getName() + "--->获取到连接:" + conns.getFirst() + " 已创建连接数量:" + createdCount + " 空闲连接数" + (conns.size() - 1));
return conns.removeFirst();
}
//如果没有空连接,则调用自动增长方法
if (createdCount < maxCount) {
autoAdd();
return getConnection();
}
//如果连接池连接数量达到上限,则等待连接归还
System.out.println(Thread.currentThread().getName() + "--->连接池中连接已用尽,请等待连接归还");
try {
monitor.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
return getConnection();
}
}
/**
* 归还连接
*
* @param conn
*/
public void returnConnection(Connection conn) {
synchronized (monitor) {
System.out.println(Thread.currentThread().getName() + "--->准备归还数据库连接" + conn);
conns.add(conn);
monitor.notify();
autoReduce();
}
}
/**
* 返回可用连接数量
*
* @return
*/
public int getCreatedCount() {
return createdCount;
}
}
4.utils工具类
4.1 ReflectUtils
/**
* @author xxbb
*/
public class ReflectUtils {
/**
* 通过数据库字段名反射调用其po类对应的set方法
*
* @param object po类对象
* @param columnName 字段名
* @param value 字段的值
*/
public static void invokeSet(Object object, String columnName, Object value) {
Class<?> clazz = object.getClass();
Method method = null;
try {
method = clazz.getDeclaredMethod("set" + StringUtils.columnNameToMethodName(columnName), value.getClass());
method.invoke(object, value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2 Resources
/**
* @author xxbb
*/
public class Resources {
/**
* 通过静态方法获取配置文件的输入流对象
* @param xmlName 配置文件名称
* @return 流对象
*/
public static InputStream getResourcesAsStream(String xmlName){
return Resources.class.getClassLoader().getResourceAsStream(xmlName);
}
}
4.3 StringUtils
/**
* 封装了字符串常用的操作
*
* @author xxbb
*/
public class StringUtils {
/**
* 正则表达式 用于匹配下划线
*/
private static Pattern linePattern = Pattern.compile("_(\\w)");
/**
* 正则表达式 用于匹配大写字母
*/
private static Pattern humpPattern = Pattern.compile("[A-Z]");
/**
* 将传入的表名去除t_字符,转化为类名
*
* @param str 传入字段
* @return 取出t_的下划线转驼峰+首字母大写字段
*/
public static String tableNameToClassName(String str) {
return firstCharToUpperCase(lineToHump(str.substring(2)));
}
/**
* 将类名转化为数据库表名
*
* @param str 传入类名
* @return 数据库表名
*/
public static String classNameToTableName(String str) {
return "t_" + humpToLine(str);
}
/**
* 将数据库字段名转化为类的命名规则,即下划线改驼峰+首字母大写,例如要获取if_freeze字段的方法,方法为getIfFreeze(),
*
* @param str 传入字段
* @return 下划线改驼峰+首字母大写
*/
public static String columnNameToMethodName(String str) {
return firstCharToUpperCase(lineToHump(str));
}
/**
* 将传入字符串的首字母大写
*
* @param str 传入字符串
* @return 首字母大写的字符串
*/
public static String firstCharToUpperCase(String str) {
return str.toUpperCase().substring(0, 1) + str.substring(1);
}
/**
* 下划线转驼峰
*
* @param str 待转换字符串
* @return 驼峰风格字符串
*/
public static String lineToHump(String str) {
//将小写转换
String newStr = str = str.toLowerCase();
Matcher matcher = linePattern.matcher(newStr);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* 驼峰转下划线
*
* @param str 待转换字符串
* @return 下划线风格字符串
*/
public static String humpToLine(String str) {
//将首字母先进行小写转换
String newStr = str.substring(0, 1).toLowerCase() + str.substring(1);
//比对字符串中的大写字符
Matcher matcher = humpPattern.matcher(newStr);
StringBuffer sb = new StringBuffer();
//匹配替换
while (matcher.find()) {
matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
}
matcher.appendTail(sb);
return sb.toString();
}
public static void main(String[] args) {
String str = columnNameToMethodName(humpToLine("ifFreeze"));
System.out.println(str);
}
}
4.4 XmlConfigBuilder
/**
* 解析配置文件的工具类
* @author xxbb
*/
public class XmlConfigBuilder {
/**
* 解析配置
* @return 封装好的配置信息
*/
public Configuration parse(InputStream config){
try {
//封装数据库配置和mapper映射配置信息
Configuration configuration = new Configuration();
//通过jsoup解析
Document document= Jsoup.parse(config,"UTF-8","");
//解析数据库配置文件信息
configuration.setEnvironment(parseEnvironment(document));
//解析mapper文件信息
configuration.setMapperStatementMap(parseMapperStatementMap(document));
return configuration;
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}finally {
try{
config.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 解析xml中数据库配置环境信息
* @param document xml的数据对象
* @return 封装好的数据库配置环境对象
*/
private Environment parseEnvironment(Document document){
//解析数据库配置信息
Environment environment=new Environment();
//目前只简单解析第一个配置
Element dataSource=document.getElementsByTag("dataSource").get(0);
for(Element element:dataSource.getElementsByTag("property")){
String name=element.attr("name");
if("driver".equals(name)){
environment.setDriver(element.val());
}else if("url".equals(name)){
environment.setUrl(element.val());
}else if("username".equals(name)){
environment.setUsername(element.val());
}else if("password".equals(name)){
environment.setPassword(element.val());
}
}
return environment;
}
/**
* 解析xml中mapper映射文件信息
* @param document xml的数据对象
* @return 封装好mapper映射的map
*/
private Map<String, MapperStatement> parseMapperStatementMap(Document document){
//存放mapper的map
Map<String, MapperStatement> mapperStatementMap=new HashMap<>(10);
Element mappers=document.getElementsByTag("mappers").get(0);
for(Element element:mappers.getElementsByTag("mapper")){
String resource=element.attr("resource");
if(resource!=null&&!"".equals(resource)){
//解析每一个mapper文件,将每一条sql语句的标签都封装到MapperStatement对象中
String mapperFilePath=
Objects.requireNonNull(this.getClass().getClassLoader().getResource(resource)).getPath();
try {
Document mapperDocument=Jsoup.parse(new File(mapperFilePath),"UTF-8");
//获取命名空间
Element namespaceElement=mapperDocument.getElementsByAttribute("namespace").get(0);
String namespace=namespaceElement.attr("namespace");
//获取CRUD标签,并将其封装到mapperStatementMap中
//查询
Elements selects=mapperDocument.getElementsByTag("select");
parseSql(mapperStatementMap, namespace, selects);
//添加
Elements inserts=mapperDocument.getElementsByTag("insert");
parseSql(mapperStatementMap, namespace, inserts);
//修改
Elements updates=mapperDocument.getElementsByTag("update");
parseSql(mapperStatementMap, namespace, updates);
//删除
Elements deletes=mapperDocument.getElementsByTag("delete");
parseSql(mapperStatementMap, namespace, deletes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return mapperStatementMap;
}
/**
* 将从mapper.xml中解析出每一条sql标签封装到mapperStatementMap中
* @param mapperStatementMap 存放所有sql语句标签信息的map对象
* @param namespace 该mapper.xml的命名空间
* @param elements 解析出的sql标签组,分select、update、insert、delete四种
*/
private void parseSql(Map<String, MapperStatement> mapperStatementMap, String namespace, Elements elements) {
MapperStatement mapperStatement;
for(Element element:elements){
mapperStatement=new MapperStatement();
String id=element.attr("id");
mapperStatement.setNamespace(namespace);
mapperStatement.setId(element.attr("id"));
mapperStatement.setSql(element.html());
//无论是否该标签是否存在或者是否有值,它都会返回一个字符串(可能是一个空的字符串),所以我们要对其空字符串进行判断
if(!"".equals(element.attr("parameterType"))){
mapperStatement.setParameterType(element.attr("parameterType"));
}
if(!"".equals(element.attr("resultType"))){
mapperStatement.setResultType(element.attr("resultType"));
}
//封装的Map中
String key=namespace+"."+id;
mapperStatementMap.put(key,mapperStatement);
}
}
}