手写Mybatis框架
第一步
创建maven项目,引入相关依赖,打包方式改为jar
1 2 3 4 5 6 7 8 9 10 11 12 13
| <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.2.0</version> </dependency> </dependencies>
|
第二步
创建Resources工具类,获得核心配置文件。再创建SqlSessionFactoryBuilder类和SqlSessionFactory类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package org.god.ibatis.utils;
import java.io.InputStream;
public class Resources { private Resources(){}
public static InputStream getResource(String resource){ InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resource); return resourceAsStream; }
}
|
第三步
设计SqlSessionFactoryBuilder和SqlSessionFactory类。
- 通过build方法创建SqlSessionFactory对象,所以SqlSessionFactoryBuilder中有一个build方法。
- build方法中肯定有参数,这些参数需要用来初始化SqlSessionFactory对象
- 思考SqlSessionFactory中会有哪些属性。通过观察核心配置文件可知(一个environment对应一个SqlSessionFactory对象),SqlSessionFactory中应该有事务管理属性、数据源属性以及sqlMapper属性。其中前两个属性都可以封装为一个对象,最后一个属性其实对应的就是mapper标签(用于关联sqlMapper.xml文件的)。mapper标签可能有多个,而且mapper.xml文件中的sql语句也会有多条,用对象来维护不可行。解决方案:使用map集合来维护,key值存放sqlId,value存放一个sqlMapperStatement对象(封装了sql语句以及其他属性)
- 因此,在SqlSessionFactory类中应该有3个属性:1. 事务管理器对象 2. 数据源对象 3. map集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <package name="com.deng.mybatis.mapper"/> </mappers>
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package org.god.ibatis.core;
import java.io.InputStream;
public class SqlSessionFactoryBuilder { public SqlSessionFactoryBuilder(){}
public SqlSessionFactory build(InputStream resource){ SqlSessionFactory factory = new SqlSessionFactory(); return factory; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package org.god.ibatis.core;
import java.util.Map;
public class SqlSessionFactory {
private Map<String,SqlMapperStatement> mapperStatements; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package org.god.ibatis.core;
public class SqlMapperStatement { private String sqlId; private String resultType;
public SqlMapperStatement(){
}
public SqlMapperStatement(String sqlId,String resultType){ this.sqlId = sqlId; this.resultType = resultType; }
public String getSqlId() { return sqlId; }
public void setSqlId(String sqlId) { this.sqlId = sqlId; }
public String getResultType() { return resultType; }
public void setResultType(String resultType) { this.resultType = resultType; }
public String toString(){ return "sqlId=" + sqlId + " resultType=" + resultType; } }
|
第四步
抽取(设计)事务管理器属性。
我们知道在mybatis中有两种事务状态:JDBC和MANAGED。因此不能直接在SqlSessionFactory中写死或者采用都写上的方法(太不美观)。可以使用多态的方法,定义一个父接口transcation,让这两个类都去实现该接口,根据用户的参数动态绑定相关类和方法。在父接口中提供管理事务方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package org.god.ibatis.core;
public interface Transaction { void commit();
void rollback();
void close(); }
|
第五步
进一步思考:如何管理事务?需要通过connection对象,也就是说我们需要在事务管理器中获得connection对象。我们很快就想到了数据源,因为它的定义就是提供connection对象的都可以称为数据源。那么进一步思考,既然在事务管理器中有数据源对象,那么SqlSessionFactory中还有必要有数据源属性吗?当然没必要,因为其可以通过Transaction对象获取数据源。
进一步思考:如何设计数据源对象?由于数据源也有3个属性:POOLED、UNPOOLED和JNDI。因此也可以采用多态的方式,定义一个父接口。并且在jdk中已经定义了一个数据源接口:javax.sql.DataSource,任何数据源对象都要实现该接口,这是一个规范。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| 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;
public class UnPooledDataSource implements javax.sql.DataSource{ private String driver; private String url; private String username; private String password;
public UnPooledDataSource(String driver,String url,String username,String password){ try { Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } this.password = password; this.url = url; this.username = username; }
@Override public Connection getConnection() throws SQLException { Connection connection = DriverManager.getConnection(url, username, password);
return c; }
@Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; }
@Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; }
@Override public Connection getConnection(String username, String password) throws SQLException { return null; }
@Override public int getLoginTimeout() throws SQLException { return 0; }
@Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; }
@Override public PrintWriter getLogWriter() throws SQLException { return null; }
@Override public void setLoginTimeout(int seconds) throws SQLException {
}
@Override public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override public boolean equals(Object obj) { return super.equals(obj); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package org.god.ibatis.core;
import javax.sql.DataSource;
public class JdbcTransaction implements Transaction{ private DataSource dataSource; private boolean autoCommit; @Override public void close() {
}
@Override public void commit() {
}
@Override public void rollback() {
} }
|
第六步
完成SqlSessionFactoryBuilder的编写,测试能否成功创建SqlSessionFactory对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
| package org.god.ibatis.core;
import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.god.ibatis.utils.Resources;
import javax.sql.DataSource; import javax.xml.crypto.Data; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;
public class SqlSessionFactoryBuilder { public SqlSessionFactoryBuilder(){}
public SqlSessionFactory build(InputStream resource){ SqlSessionFactory factory = null; Transaction transaction = null; DataSource dataSource = null; Map<String,SqlMapperStatement>map = null; List<String>sqlMapperXmlPath = new ArrayList<>(); try { SAXReader reader = new SAXReader(); Document document = reader.read(resource); Element configuration = document.getRootElement(); Element environments = configuration.element("environments"); String aDefault = environments.attributeValue("default"); List<Element> environmentlist = environments.elements("environment"); Element transactionElm = null; Element dataSourceElm = null; for(int i = 0; i < environmentlist.size(); i ++){ Element e = environmentlist.get(i); if(e.attributeValue("id").equals(aDefault)){ transactionElm = e.element("transactionManager"); dataSourceElm = e.element("dataSource"); break; } } Element mappers = configuration.element("mappers"); List<Element> mapper = mappers.elements("mapper"); mapper.forEach(mappert ->{ String s = mappert.attributeValue("resource"); sqlMapperXmlPath.add(s); }); dataSource = getDataSource(dataSourceElm); transaction = getTransaction(transactionElm,dataSource); map = getMap(sqlMapperXmlPath); factory = new SqlSessionFactory(transaction,map); } catch (Exception e) { e.printStackTrace(); } return factory; }
private Transaction getTransaction(Element transactionElm,DataSource dataSource){ Transaction transaction = null; String type = transactionElm.attributeValue("type").trim().toUpperCase();
if (constant.JDBC_TRANSACTION.equals(type)) { transaction = new JdbcTransaction(dataSource,false); }else if(constant.MANAGED_TRANSACTION.equals(type)){
}
return transaction; }
public DataSource getDataSource(Element dataSourceElm){ String type = dataSourceElm.attributeValue("type").trim().toUpperCase(); Map<String,String> map = new HashMap<>(); List<Element> properties = dataSourceElm.elements("property"); properties.forEach(property -> { String name = property.attributeValue("name"); String value = property.attributeValue("value"); map.put(name,value); }); DataSource dataSource = null; if (constant.POOLED_DATASOURCE.equals(type)) {
}else if(constant.UNPOOLED_DATASOURCE.equals(type)){ dataSource = new UnPooledDataSource(map.get("driver"),map.get("url"),map.get("username"),map.get("password")); }else if(constant.JNDI_DATASOURCE.equals(type)){
}
return dataSource; }
private Map<String,SqlMapperStatement> getMap(List<String>sqlMapperXmlPath){ Map<String,SqlMapperStatement>map = new HashMap<>(); sqlMapperXmlPath.forEach(path -> { try { SAXReader reader = new SAXReader(); Document document = reader.read(Resources.getResource(path)); Element mapper = document.getRootElement(); String namespace = mapper.attributeValue("namespace"); List<Element> tags = mapper.elements(); tags.forEach(tag -> { String id = tag.attributeValue("id"); String resultType = tag.attributeValue("resultType"); String sqlId = namespace + "." + id; String sql = tag.getTextTrim(); SqlMapperStatement sqlMapperStatement = new SqlMapperStatement(sql, resultType); map.put(sqlId,sqlMapperStatement); }); } catch (Exception e) { e.printStackTrace(); } });
return map; } }
|
第七步
设计SqlSession类。通过SqlSessionFactory的openSession方法创建SqlSession对象。
因为需要通过SqlSession执行sql语句,所以SqlSession中必须要有connection对象和sql语句。这些都可以通过SqlSessionFactory对象获得,因此往SqlSession对象中传入SqlSessionFactory对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package org.god.ibatis.core;
import java.sql.Connection; import java.util.Map;
public class SqlSession { private SqlSessionFactory sqlSessionFactory;
public SqlSession(){}
public SqlSession(SqlSessionFactory sqlSessionFactory){ this.sqlSessionFactory = sqlSessionFactory; }
public void commit(){ sqlSessionFactory.getTransaction().commit(); }
public void rollback(){ sqlSessionFactory.getTransaction().rollback(); }
public void close(){ sqlSessionFactory.getTransaction().close(); } }
|
第八步
在SqlSession类中编写执行sql语句的方法。这里只实现insert和selectOne方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| package org.god.ibatis.core;
import java.lang.reflect.Method; import java.sql.*; import java.util.Map;
public class SqlSession { private SqlSessionFactory sqlSessionFactory;
public SqlSession(){}
public SqlSession(SqlSessionFactory sqlSessionFactory){ this.sqlSessionFactory = sqlSessionFactory; }
public int insert(String sqlId,Object object){ int count = 0; try { Connection connection = sqlSessionFactory.getTransaction().getConnection(); Map<String, SqlMapperStatement> mapperStatements = sqlSessionFactory.getMapperStatements(); SqlMapperStatement sqlMapperStatement = mapperStatements.get(sqlId); String sql = sqlMapperStatement.getSql(); String sql1 = sql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?"); PreparedStatement preparedStatement = connection.prepareStatement(sql1); int index = 1; int fromIndex = 0; while(true){ int jingIndex = sql.indexOf("#",fromIndex); if(jingIndex < 0){ break; } int youkuoIndex = sql.indexOf("}"); String propertyName = sql.substring(jingIndex + 2, youkuoIndex).trim(); String methodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1); Method getMethod = object.getClass().getDeclaredMethod(methodName); Object propertyValue = getMethod.invoke(object); preparedStatement.setString(index,propertyValue.toString()); index++; } count = preparedStatement.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } return count; }
public Object selectOne(String sqlId,Object param){ Object obj = null; try { SqlMapperStatement sqlMapperStatement = sqlSessionFactory.getMapperStatements().get(sqlId); String godbatisSql = sqlMapperStatement.getSql(); String resultType = sqlMapperStatement.getResultType();
String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9]*}", "?"); Connection connection = sqlSessionFactory.getTransaction().getConnection(); PreparedStatement ps = connection.prepareStatement(sql); ps.setString(1,param.toString()); ResultSet resultSet = ps.executeQuery(); if (resultSet.next()) { ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); Class<?> resultClass = Class.forName(resultType); obj = resultClass.newInstance(); for(int i = 0; i <= columnCount; i ++){ String columnName = metaData.getColumnName(i + 1).trim(); String methodName = "set" + columnName.toUpperCase().charAt(0) + columnName.substring(1); Method setMethod = obj.getClass().getDeclaredMethod(methodName, String.class); setMethod.invoke(obj,resultSet.getString(columnName)); } } } catch (Exception e) { e.printStackTrace(); } return obj; } public void commit(){ sqlSessionFactory.getTransaction().commit(); }
public void rollback(){ sqlSessionFactory.getTransaction().rollback(); }
public void close(){ sqlSessionFactory.getTransaction().close(); } }
|