目录
详细jar包见我的资源,免费下载。
一、该框架局限性:
- 数据库表列名必须是varchar。
- 本手写框架只实现了JDBC的事务管理器和UNPOOLED的数据源属性。
- 本手写框架仅支持insert操作和单条件查询。
二、框架结构:
三、实现思路:
四、实现类:
Resources工具类:
package org.god.ibatis.util;
import java.io.InputStream;
/**
* 工具类,完成类路径中资源的加载
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class Resources {
/**
* 工具类中的构造方法要私有化
*/
private Resources() {}
/**
* 从类路径中加载资源,工具类中的方法要是静态的
* @param resource 放在类路径中的资源文件
* @return 返回一个指向资源文件的输入流
*/
public static InputStream getResourcesAsStream(String resource){
return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
}
}
Const常量类:
package org.god.ibatis.core;
/**
* 整个框架的常量类,保证OCP原则,修改时不必修改底层代码
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class Const {
public static final String UN_POOLED_DATASOURCE = "UNPOOLED";
public static final String POOLED_DATASOURCE = "POOLED";
public static final String JNDI_DATASOURCE = "JNDI";
public static final String JDBC_TRANSACTION = "JDBC";
public static final String MANAGED_TRANSACTION = "MANAGED";
}
MappedStatement POJO类:
package org.god.ibatis.core;
/**
* POJO类
* 一个MappedStatement对象封装了一个SQL标签的所有信息:<insert>||<update>||<delete>||<select>
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class MappedStatement {
/**
* sql语句
*/
private String sql;
/**
* 入参类型
*/
private String parameterType;
/**
* 结果集类型
*/
private String resultType;
public MappedStatement(String sql, String parameterType, String resultType) {
this.sql = sql;
this.parameterType = parameterType;
this.resultType = resultType;
}
public MappedStatement() {
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
@Override
public String toString() {
return "MappedStatement{" +
"sql='" + sql + '\'' +
", parameterType='" + parameterType + '\'' +
", resultType='" + resultType + '\'' +
'}';
}
}
SqlSessionFactoryBuilder类:
package org.god.ibatis.core;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.god.ibatis.util.Resources;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.*;
/**
* SqlSessionFactory构建器类
* 通过SqlSessionFactoryBuilder的build方法解析SqlMapConfig.xml文件
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class SqlSessionFactoryBuilder {
/**
*无参数构造方法,用于创建对象
*/
public SqlSessionFactoryBuilder() {
}
/**
* 解析SqlMapConfig.xml文件,构造SqlSessionFactory对象,本类后面的三个get方法都是为该方法服务的
* @param in 指向SqlMapConfig.xml文件的输入流
* @return 返回SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream in){
SqlSessionFactory factory = null;
try {
//解析SqlMapConfig.xml核心配置文件
SAXReader reader = new SAXReader();
Document document = reader.read(in);
//获取environments标签及其属性
Element environments = (Element) document.selectSingleNode("/configuration/environments");
String defaultId = environments.attributeValue("default");
//获取environment标签及其子标签transactionManager、dataSource
Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
Element transactionElt = environment.element("transactionManager");
Element dataSourceElt = environment.element("dataSource");
//获取所有Mapper.xml文件并加入到SQLMapperXMLPathList集合中
List<String> SQLMapperXMLPathList = new ArrayList<>();
List<Node> mappers = document.selectNodes("//mapper");
for (Node Tmapper : mappers) {
Element mapper = (Element)Tmapper;
String resource = mapper.attributeValue("resource");
SQLMapperXMLPathList.add(resource);
}
//获取数据源,为什么要获取数据源?因为创建事务管理器需要数据源
DataSource dataSource = getDataSource(dataSourceElt);
//获取事务管理器
Transaction transaction = getTransaction(transactionElt,dataSource);
//获取SQL集合
Map<String,MappedStatement> mappedStatements = getMappedStatements(SQLMapperXMLPathList);
//传入事务管理器和SQL集合,创建SqlSessionFactory
factory = new SqlSessionFactory(transaction,mappedStatements);
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
/**
* 解析所有Mapper.xml文件,然后构建Mapper集合
* @param SQLMapperXMLPathList 所有Mapper.xml文件名
* @return SQL集合
*/
private Map<String, MappedStatement> getMappedStatements(List<String> SQLMapperXMLPathList) {
//存放所有xml文件的所有SQL语句的信息
Map<String,MappedStatement> mappedStatements = new HashMap<>();
//遍历所有Mapper.xml文件并添加所有sql标签内容到SQL集合mappedStatements中
for (String SQLMapperXMLPath : SQLMapperXMLPathList) {
try {
SAXReader reader = new SAXReader();
Document document = reader.read(Resources.getResourcesAsStream(SQLMapperXMLPath));
//获取该Mapper.xml文件下的mapper标签
Element mapper = (Element)document.selectSingleNode("mapper");
//获取mapper标签的namespace属性
String namespace = mapper.attributeValue("namespace");
//获取mapper标签下所有sql标签
List<Element> elements = mapper.elements();
//遍历sql标签内容添加到SQL集合mappedStatements中
for (Element element : elements) {
//获取属性
String id = element.attributeValue("id");
String sqlid = namespace+"."+id;//拼接,区分不同mapper.xml文件下相同id的标签,防止id冲突
String parameterType = element.attributeValue("parameterType");
String resultType = element.attributeValue("resultType");
//获取标签内容(sql语句)
String sql = element.getText().trim();
//将单个SQL标签的信息封装进我们创建的POJO对象中
MappedStatement mappedStatement = new MappedStatement(sql,parameterType,resultType);
//将该SQL标签的内容添加到SQL集合mappedStatements中
mappedStatements.put(sqlid,mappedStatement);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return mappedStatements;
}
/**
* 获取事务管理器对象
* @param transactionElt 事务管理器标签元素<transactionManager>
* @param dataSource 数据源对象
* @return Transaction
*/
private Transaction getTransaction(Element transactionElt, DataSource dataSource) {
//存储创建的事务管理器对象
Transaction transaction = null;
//JDBC||MANAGED,根据不同属性值创建不同事务管理器对象
String type = transactionElt.attributeValue("type").trim().toUpperCase();
if (Const.JDBC_TRANSACTION.equals(type)) {
transaction = new JdbcTransaction(dataSource,false);//默认开启事务
}
if (Const.MANAGED_TRANSACTION.equals(type)) {
transaction = new ManagedTransaction();
}
return transaction;
}
/**
* 获取数据源对象
* @param dataSourceElt 数据源标签元素<dataSource>
* @return DataSource
*/
private DataSource getDataSource(Element dataSourceElt) {
//存储创建的数据源对象
DataSource dataSource = null;
//获取所有property标签
Map<String,String> map= new HashMap<>();
//获取所有property标签
List<Element> propertyElts = dataSourceElt.elements("property");
//遍历所有property标签得到属性值并加入到map集合中,属性值就是数据库登录信息
for (Element propertyElt : propertyElts) {
String name = propertyElt.attributeValue("name");
String value = propertyElt.attributeValue("value");
map.put(name,value);
}
//UNPOOLED||POOLED||JNDI,根据不同属性值创建不同数据源对象
String type = dataSourceElt.attributeValue("type").trim().toUpperCase();
if (Const.UN_POOLED_DATASOURCE.equals(type)) {
dataSource = new UnPooledDataSource(map.get("driver"),map.get("url"),map.get("username"),map.get("password"));
}
if (Const.POOLED_DATASOURCE.equals(type)) {
dataSource = new PooledDataSource();
}
if (Const.JNDI_DATASOURCE.equals(type)) {
dataSource = new JNDIDataSource();
}
return dataSource;
}
}
SqlSessionFactory类:
package org.god.ibatis.core;
import javax.sql.DataSource;
import java.util.Map;
/**
* 一个数据库对应一个SqlSessionFactory对象,对象中应存储:用户选择的事务管理器类型,用户针对该数据库编写的所有SQL语句,数据库连接信息
* 通过SqlSessionFactory对象可以获取SqlSession对象(开启会话)
* 一个SqlSessionFactory对象可获取多个SqlSession会话对象
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class SqlSessionFactory {
/**
* 事务管理器属性<transactionManager>,用来管理事务提交、回滚、关闭的
* 这里不确定用户到底是用JDBC还是MANAGED,所以我们使用接口,用户使用哪个就多态指向哪个实现类
*/
private Transaction transaction;
/**
* 存放所有SQL语句的Map集合
* key是sqlId
* value是sqlId对应的SQL标签的信息(属性,标签内容),用MappedStatment对象封装
*/
private Map<String,MappedStatement> mappedStatements;
/**
* 这里为了自动连接数据库,应该还有一个数据库连接信息<dataSource>,但是这个这些信息在transaction实现类中已经创建了,所以这里我们就不用再创建了
*/
//private DataSource dataSource;
/**
* 无参构造方法和有参构造方法
*/
public SqlSessionFactory() {
}
public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatements) {
this.transaction = transaction;
this.mappedStatements = mappedStatements;
}
/**
* 获取SQL会话对象SqlSession
* @return
*/
public SqlSession openSession(){
//开启连接,给connection对象赋值
transaction.openConnection();
//我们还需要把transaction传给sqlSession,方便sqlSession调用transaction的getConnection()方法
//我们需要把我们的SQL集合传给sqlSession,这样sqlSession才能执行sql语句
SqlSession sqlSession = new SqlSession(this);
return sqlSession;
}
public Transaction getTransaction() {
return transaction;
}
public Map<String, MappedStatement> getMappedStatements() {
return mappedStatements;
}
}
SqlSession类:
package org.god.ibatis.core;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.Locale;
/**
* 专门负责执行SQL语句的对象
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class SqlSession {
/**
* SqlSessionFactory的openSqlSession()方法会创建SqlSession对象,
* 并将SqlSessionFactory及其属性事务管理器对象transaction,SQL语句集合mappedStatements传过来
*/
private SqlSessionFactory factory;
public SqlSession(SqlSessionFactory factory) {
this.factory = factory;
}
/**
* 执行insert语句
* @param sqlId sql语句的Id,sqlId = namespace + 标签id
* @param pojo 用户想插入的数据,是用户自己传的,Object类型表示可以接受任意类型数据
* @return
*/
public Integer insert(String sqlId,Object pojo){
int count = 0;
try {
//获取连接,见UnPooledDataSource类的getConnection()方法,就是JDBC获取连接的方法
Connection connection = factory.getTransaction().getConnection();
//get(sqlId)是map集合的方法,获取到sqlId对应的MappedStatement对象,通过MappedStatement对象的getSql()方法获得对象内存放的sql语句
String oldsql=factory.getMappedStatements().get(sqlId).getSql();
//将原来的insert into student(name,email,age) values(#{name},#{email},#{age})使用正则表达式
//替换成insert into student(name,email,age) values(?,?,?)
String sql = oldsql.replaceAll("#\\{[a-zA-Z0-9_$]*}","?");
PreparedStatement ps = connection.prepareStatement(sql);
//这里的关键在于,你预先不知道用预编译的SQL里有几个?,也不知道用户传来的参数与哪个?对应
//给?传值,并解析sql语句需要传值的位置和需要传值的参数名
int fromIndex = 0;
int index = 1;//表示这是第几个问号,即需要传值的位置
while(true){
int jingHaoIndex = oldsql.indexOf("#",fromIndex);
if(jingHaoIndex<0){
break;//退出死循环
}
int youKuoHaoIndex = oldsql.indexOf("}",fromIndex);
String propertyName = oldsql.substring(jingHaoIndex+2,youKuoHaoIndex).trim();//第index个问号中占位符的值
fromIndex = youKuoHaoIndex+1;
//已知属性名propertyName,通过调用用户自定义pojo类的get方法获取属性值
//这里使用拼接方法名的方式获取相应的get方法得到要给该?传的参数,getMethodName就是我们拼成的getXXX()方法
String getMethodName = "get"+propertyName.toUpperCase().charAt(0)+propertyName.substring(1);
//反射执行方法
Method getMethod = pojo.getClass().getDeclaredMethod(getMethodName);
Object propertyValue = getMethod.invoke(pojo);
ps.setString(index,propertyValue.toString());
index++;
}
//执行sql
count = ps.executeUpdate();
} catch (Exception throwables) {
throwables.printStackTrace();
}
return count;
}
/**
* 执行查询语句,返回一个对象,只能返回一行
* @param sqlId
* @param param
* @return
*/
public Object selectOne(String sqlId,Object param){
Object obj = null;
try {
//获取连接
Connection connection = factory.getTransaction().getConnection();
//get(sqlId)是map集合的方法,获取到sqlId对应的MappedStatement对象,通过MappedStatement对象的getSql()方法获得对象内存放的sql语句
MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId);
//sql语句
String oldsql = mappedStatement.getSql();
String sql = oldsql.replaceAll("#\\{[a-zA-Z0-9_$]*}","?").trim();
PreparedStatement ps = connection.prepareStatement(sql);
//给占位符传值,这里只有一个占位符
ps.setString(1,param.toString());
ResultSet resultSet = ps.executeQuery();
//要封装的结果类型
String resultType = mappedStatement.getResultType();
if (resultSet.next()) {
//反射获取class
Class resultTypeClass = Class.forName(resultType);
//调用无参构造方法创建对象
obj = resultTypeClass.newInstance();
//给属性赋值
//这里的关键在于SQL执行返回了一列数据,但是这列数据包含什么内容我们预先不知道(不知道列名),各个数据应该传给POJO对象的哪个属性是关键
ResultSetMetaData rsmd = resultSet.getMetaData();
//获取列数
int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
//根据列名拼接set的方式获得set方法,给POJO对象的属性赋值
String propertyName = rsmd.getColumnName(i);
//这里的setMethodName就是我们拼接成的setXXX()方法
String setMethodName = "set"+propertyName.toUpperCase().charAt(0)+propertyName.substring(1);
Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName,String.class);
//调用我们拼接的set方法,传入该列对应的查询结果
setMethod.invoke(obj,resultSet.getString(propertyName));
}
}
} catch (Exception throwables) {
throwables.printStackTrace();
}
return obj;
}
/**
* 获取factory的属性transaction,通过该事务管理器transaction的commit()方法提交事务
*/
public void commit(){
factory.getTransaction().commit();
}
/**
* 获取factory的属性transaction,通过该事务管理器transaction的commit()方法回滚事务
*/
public void rollback(){
factory.getTransaction().rollback();
}
/**
* 获取factory的属性transaction,通过该事务管理器transaction的commit()方法关闭事务
*/
public void close(){
factory.getTransaction().close();
}
}
Transaction接口:
package org.god.ibatis.core;
import java.sql.Connection;
/**
* 事务管理器接口,用来管理事务提交、回滚、关闭的
* 所有事务管理器(JDBC,MANAGED)都应该遵循该接口
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public interface Transaction {
/**
* 提交事务
*/
public void commit();
/**
* 回滚事务
*/
public void rollback();
/**
* 关闭事务
*/
public void close();
/**
* 开启connection连接
*/
public void openConnection();
/**
* 获取Transaction的connection连接对象
*/
public Connection getConnection();
}
JdbcTransaction类:该框架已实现
package org.god.ibatis.core;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* JDBC事务管理器,用JDBC来管理事务提交、回滚、关闭的,实现Transaction接口
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class JdbcTransaction implements Transaction{
/**
* 数据源属性<dataSource>,封装了用户登录数据库的信息
* 这里不确定用户到底是用POOLED、UNPOOLED、JNDI,所以我们使用接口,用户使用哪个就多态指向哪个实现类
*/
private DataSource dataSource;
/**
* 事务是否自动提交标志
* true自动提交
* false手动提交
*/
private boolean autoCommit;
/**
* 数据库连接对象,通过openConnection方法给connection赋值
*/
private Connection connection;
/**
* 根据数据源和是否自动提交标志创建事务管理器
* @param dataSource 数据源,有三种实现类
* @param autoCommit 是否自动提交标志
*/
public JdbcTransaction(DataSource dataSource, boolean autoCommit) {
this.dataSource = dataSource;
this.autoCommit = autoCommit;
}
/**
* 获取dataSource的连接对象
*/
public void openConnection(){
if (connection == null) {
try {
connection = dataSource.getConnection();//这里调用的dataSource接口的getConnection,他有三个实现类
//开启事务
connection.setAutoCommit(autoCommit);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
/**
* 方便其他类使用JdbcTransaction这个类的对象时获取JdbcTransaction下的connection
* @return 本类中封装的connection
*/
@Override
public Connection getConnection() {
return connection;
}
/**
* 事务提交
*/
@Override
public void commit() {
try {
connection.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 事务回滚
*/
@Override
public void rollback() {
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
/**
* 事务关闭
*/
@Override
public void close() {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
ManagedTransaction类:该框架未实现
package org.god.ibatis.core;
import java.sql.Connection;
/**
* MANAGED事务管理器,用来管理事务提交、回滚、关闭的,实现Transaction接口
* 因为我们的godbatis比较简单,所以我们不对这个复杂的类进行实现。
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class ManagedTransaction implements Transaction{
@Override
public void commit() {
}
@Override
public void rollback() {
}
@Override
public void close() {
}
@Override
public void openConnection() {
}
@Override
public Connection getConnection() {
return null;
}
}
UnPooledDataSource类:该框架已实现
package org.god.ibatis.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 数据源实现类:UNPOOLED
* 该实现类特点:不使用连接池,每次都新建connection对象
* javax.sql.DataSource是JDK自带的数据源接口,所以我们不用再去写一个像Transaction一样的接口
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class UnPooledDataSource implements javax.sql.DataSource{
private String url;
private String username;
private String password;
/**
* 创建一个数据源对象
* @param driver
* @param url
* @param username
* @param password
*/
public UnPooledDataSource(String driver, String url, String username, String password) {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url,username,password);
return connection;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
PooledDataSource类:该框架未实现
package org.god.ibatis.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 数据源实现类:POOLED
* 该实现类特点:使用连接池,每次都从数据库连接池获取connection对象
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class PooledDataSource implements javax.sql.DataSource{
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
JNDIDataSource类:该框架未实现
package org.god.ibatis.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 数据源实现类:JNDI
* 该实现类特点:使用第三方数据库连接池
* @author 姓蔡小朋友
* @since 1.0
* @version 1.0
*/
public class JNDIDataSource implements javax.sql.DataSource {
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}