自定义持久层框架和mybatis源码分析
JDBC问题分析:
1.数据库配置信息存在硬编码(修改源代码再打包部署)
2.频繁创建释放数据库连接
3.手动封装结果集 繁琐
自定义持久层框架:
1.创建Persistence IPersistence两个模块
Persistence是自定义持久层框架的具体实现。
IPersistence是用户端存放sqlMapConfig.xml和mapper.xml端。
2.IPersistence编写
1.sqlMapConfig.xml配置
<configuration>
<dataSource>
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
<mapper resoure="UserMapper.xml"></mapper>
</configuration>
2.mapper.xml配置
<mapper namespace="com.fq.dao.UserDao">
<select id="selectOne" resultType="com.fq.pojo.User" paramType="com.fq.pojo.User">
select * from user where username = #{username}
</select>
<select id="selectAll" resultType="com.fq.pojo.User">
select * from user
</select>
<update id="update" paramType="com.fq.pojo.User" resultType="java.lang.Integer">
update `user` set `username` = #{username} where `id` = #{id}
</update>
<insert id="insert" paramType="com.fq.pojo.User" resultType="java.lang.Integer">
insert into `user` (`username`) values (#{username})
</insert>
<delete id="delete" paramType="com.fq.pojo.User" resultType="java.lang.Integer">
delete from `user` where id = #{id}
</delete>
</mapper>
3.Persistence编写
1.首先编写Resource类,获取sqlMapConfig.xml文件后将该文件加载成内存输入流
public class Resource {
//使用类加载器动态加载文件 把文件保存在内存中
public static InputStream getResourceAsStream(String path){
return Resource.class.getClassLoader().getResourceAsStream(path);
}
}
2.编写sqlSessionBulid类生产sqlSessionFactory并解析sqlMapConfig.xml和mapper.xml文件,把解析后的文件放在Configrution类中,并将Configrution向
sqlSessionFactory传递
sqlSessionBulid:
public class SqlSessionFactoryBuild {
public SqlSessionFactory build (InputStream in) throws DocumentException {
//创建XMLConfigBuild对象解析内存中的sqlMapConfig.xml
XMLConfigBuild xmlConfigBuild = new XMLConfigBuild();
Configuration configuration = xmlConfigBuild.parseConfig(in);
//创建SqlSessionFactory
SqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}
XMLConfigBuild解析sqlMapConfig.xml 并获取mapper属性使用XMLMapperBuild 类解析 :
public class XMLConfigBuild {
private Configuration configuration;
public XMLConfigBuild(){
this.configuration = new Configuration();
}
/**
*解析sqlMapConfig.xml
*/
public Configuration parseConfig (InputStream inputStream) throws DocumentException {
//使用dom4j对内存的xml解析
Document document = new SAXReader().read(inputStream);
//获取根节点属性<configuration>
Element rootElement = document.getRootElement();
//找到节点下的property属性//表示只要是子节点都可以找到
List<Element> elementList = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element element : elementList) {
//获取property的name value 并使用Properties保存key value
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name,value);
}
getConfiguration(properties);
//创建XMLMapperBuild对象解析内存中的mapper.xml
XMLMapperBuild xmLmapperBuild = new XMLMapperBuild(configuration);
//获取<mapper>节点
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String resource = element.attributeValue("resoure");
InputStream in = Resource.getResourceAsStream(resource);
xmLmapperBuild.parse(in);
}
return configuration;
}
/**
* 添加连接池 并给Configuration赋值
*/
public void getConfiguration(Properties properties){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(properties.getProperty("driverClass"));
druidDataSource.setUrl(properties.getProperty("url"));
druidDataSource.setUsername(properties.getProperty("username"));
druidDataSource.setPassword(properties.getProperty("password"));
configuration.setDataSource(druidDataSource);
}
}
3.XMLMapperBuild 解析mapper.xml文件
public class XMLMapperBuild {
private Configuration configuration;
public XMLMapperBuild(Configuration configuration){
this.configuration = configuration;
}
/**
* 解析mapper.xml 代码注释请参照 XMLConfigBuild
* @param inputStream mapper.xml
*/
public void parse (InputStream inputStream) throws DocumentException {
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
//获取<mapper>的namespace属性
String namespace = rootElement.attributeValue("namespace");
//查找
//获取select标签
List<Element> selectList = rootElement.selectNodes("//select");
//存入mapperStatementMap中
setConfigurationMapperStatement(selectList,namespace);
//更新
List<Element> updateList = rootElement.selectNodes("//update");
setConfigurationMapperStatement(updateList,namespace);
//插入
List<Element> insertList = rootElement.selectNodes("//insert");
setConfigurationMapperStatement(insertList,namespace);
//删除
List<Element> deleteList = rootElement.selectNodes("//delete");
setConfigurationMapperStatement(deleteList,namespace);
}
/**
* 把<select>标签中的属性获取到并添加到Configuration 中的mapperStatementMap属性里
* @param elements 获取标签属性
* @param namespace 命名空间
*/
public void setConfigurationMapperStatement(List<Element> elements,String namespace){
for (Element element : elements) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String paramType = element.attributeValue("paramType");
String sql = element.getTextTrim();
String key = namespace + "." + id;
MapperStatement mapperStatement = new MapperStatement();
mapperStatement.setId(id);
mapperStatement.setParamType(paramType);
mapperStatement.setResultType(resultType);
mapperStatement.setSql(sql);
configuration.getMapperStatementMap().put(key , mapperStatement);
}
}
}
4.创建sqlSessionFactory工厂类 生产具体的执行SqlSession 类和封装好的Configruation
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession(){
return new DefaultSqlSession(configuration);
}
}
5.具体实现类SqlSession 该类封装一些定义好的select,insert等方法(传统开发模式),并使用动态代理实现不用实现类对接口的代理方式执行增删改查。selectOne实际调用的都是selectList,而增删改因为低层执行jdbc都是调用的是excuteUpdate();所以逻辑是一样的。
public class DefaultSqlSession implements SqlSession{
private Configuration configuration;
private SimpleExecutor simpleExecutor = new SimpleExecutor();
public DefaultSqlSession(Configuration configuration){
this.configuration = configuration;
}
@Override
public <E> List<E> selectAll(String statementId, Object... obj) throws Exception {
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
List<Object> list = simpleExecutor.query(configuration, mapperStatement, obj);
return (List<E>) list;
}
@Override
public <T> T selectOne(String statementId, Object... obj) throws Exception {
List<Object> objects = selectAll(statementId, obj);
if(objects.size()==1){
return (T) objects.get(0);
}else if(objects.size()==0){
throw new SQLSyntaxErrorException("数据库没有你要查找的数据");
}else {
throw new RuntimeException("返回结果过多");
}
}
@Override
public Integer update(String statementId, Object... obj) throws Exception {
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
Integer state = simpleExecutor.update(configuration,mapperStatement,obj);
return state;
}
@Override
public Integer insert(String statementId, Object... obj) throws Exception {
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
Integer state = simpleExecutor.insert(configuration,mapperStatement,obj);
return state;
}
@Override
public Integer delete(String statementId, Object... obj) throws Exception {
MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementId);
Integer state = simpleExecutor.delete(configuration,mapperStatement,obj);
return state;
}
/**
*
* @param mapperClass 传过来的接口
* @param <T>
* @return
*/
@Override
public <T> T getMapper(Class<?> mapperClass) {
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
//获取方法名
String methodName = method.getName();
//获取类名
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
if(methodName.contains("update")){
Object o = update(statementId,args);
return o;
}
else if(methodName.contains("select")){
//判断方法返回的类型是否带泛型
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType) {
List<Object> objects = selectAll(statementId,args);
return objects;
}
Object o = selectOne(statementId, args);
return o;
}else if(methodName.contains("insert")){
Object o = insert(statementId,args);
return o;
}else if(methodName.contains("delete")){
Object o = delete(statementId,args);
return o;
}
return null;
}
});
return (T) proxyInstance;
}
}
6.具体执行sql类(Excutor),对结果集的封装,参数的设置,以及执行sql和解析标签中的占位符#{}
public class SimpleExecutor implements Executor {
@Override
public <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... obj) throws Exception {
//获取连接
Connection connection = getConnection(configuration);
//2.获取MapperStatement对象存的sql信息
String sql = mapperStatement.getSql();
BoundSql parseSql = getParseSql(sql);
//获取预处理对象
PreparedStatement preparedStatement = connection.prepareStatement(parseSql.getSql());
//4.获取参数类型
//类全限定名
String paramType = mapperStatement.getParamType();
//获取类的字节码
Class<?> paramClass = getParamClass(paramType);
List<ParameterMapping> params = parseSql.getParams();
for (int i = 0; i < params.size() ; i++) {
ParameterMapping parameterMapping = params.get(i);
String content = parameterMapping.getContent();
Field declaredField = paramClass.getDeclaredField(content);
declaredField.setAccessible(true);
Object o = declaredField.get(obj[0]);
//设置参数
preparedStatement.setObject(i+1,o);
}
//5.执行sql
ResultSet resultSet = preparedStatement.executeQuery();
//封装结果集
//获取返回值类型
String resultType = mapperStatement.getResultType();
Class<?> resultClass = getParamClass(resultType);
ArrayList<Object> objects = new ArrayList<>();
while (resultSet.next()){
Object o = resultClass.newInstance();
//获取元数据
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
String columnName = metaData.getColumnName(i);
Object value = resultSet.getObject(columnName);
//内省
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,value);
}
objects.add(o);
}
close(preparedStatement,connection,resultSet);
return (List<E>) objects;
}
@Override
public Integer update(Configuration configuration, MapperStatement mapperStatement, Object[] obj) throws Exception {
return common(configuration,mapperStatement,obj);
}
@Override
public Integer insert(Configuration configuration, MapperStatement mapperStatement, Object[] obj) throws Exception {
return common(configuration,mapperStatement,obj);
}
@Override
public Integer delete(Configuration configuration, MapperStatement mapperStatement, Object[] obj) throws Exception {
return common(configuration,mapperStatement,obj);
}
private Class<?> getParamClass(String paramType) throws ClassNotFoundException {
//判断是否为null
if(paramType != null){
return Class.forName(paramType);
}
return null;
}
//解析占位符#{}
private BoundSql getParseSql(String sql) {
//创建解析器
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//把占位符转换为?
String parseSql = genericTokenParser.parse(sql);
//保存解析后的占位符里的内容
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
//用类保存sql信息
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
return boundSql;
}
/**
* 获取connection
*/
public Connection getConnection(Configuration configuration) throws SQLException {
//注册驱动
DataSource dataSource = configuration.getDataSource();
//创建连接
return dataSource.getConnection();
}
/**
* 关流
*/
public void close(Statement statement,Connection connection,ResultSet resultSet) throws SQLException {
statement.close();
connection.close();
if(resultSet != null){
resultSet.close();
}
}
/**
* update insert 共同方法
*/
public Integer common(Configuration configuration, MapperStatement mapperStatement, Object... obj) throws Exception {
Connection connection = getConnection(configuration);
String sql = mapperStatement.getSql();
BoundSql parseSql = getParseSql(sql);
PreparedStatement preparedStatement = connection.prepareStatement(parseSql.getSql());
String paramType = mapperStatement.getParamType();
Class<?> paramClass = getParamClass(paramType);
List<ParameterMapping> params = parseSql.getParams();
for (int i = 0; i < params.size() ; i++) {
ParameterMapping parameterMapping = params.get(i);
String content = parameterMapping.getContent();
Field declaredField = paramClass.getDeclaredField(content);
declaredField.setAccessible(true);
Object o = declaredField.get(obj[0]);
//设置参数
preparedStatement.setObject(i+1,o);
}
//5.执行sql
Integer state = preparedStatement.executeUpdate();
if(state != 1){
throw new SQLSyntaxErrorException("你的sql有误");
}
close(preparedStatement,connection,null);
return state;
}
}
6.测试类 创建sqlSessionBulid,加载sqlMapConfig.xml配置文件,创建DefultSqlSessionFactory,创建SqlSession,执行getMapper方法。
public static void main(String[] args) throws Exception {
InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactoryBuild sqlSessionFactoryBuild = new SqlSessionFactoryBuild();
SqlSessionFactory factory = sqlSessionFactoryBuild.build(resourceAsStream);
SqlSession sqlSession = factory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
userDao.selectAll();
User user = new User();
user.setId(1);
userDao.delete(user);
// User object = sqlSession.selectOne("com.fq.pojo.User.selectByUsername",user);
// List<Object> objects = sqlSession.selectAll("com.fq.pojo.User.selectAll");
// System.out.println(objects);
}
}
mybatis源码分析
1.SqlSessionFactoryBuilder
构建者模式 生产Configruation这个复杂对象,并创建DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
使用dom4j解析(XMLConfigBuilder )
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
2.Configruation
将sqlMapConfigruation.xml文件解析到Configruation类中。mapper标签是保存mapper.xml中的文件解析到MappedStatement类中。而整个Configruation不仅保存配置的配置信息(数据库连接,别名,插件等)还保存一个protected final Map<String, MappedStatement> mappedStatements = new StrictMap(“Mapped Statements collection”);很多个mapper文件解析的标签(SqlCommandType(标签名select,update),参数类型paramType,返回类型resultType,sql信息,一二级缓存信息等)
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
3.DefaultSqlSessionFactory
openSession执行的方法如下(第一个参数是具体执行的Executor的类型,默认是SimpleExecutor,其他两个暂时不介绍,第二个参数是事务隔离级别,第三个参数是是否自动提交事务)
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
4.DefaultSqlSession的getMapper()方法
newProxyInstance方法第一个参数是当前执行类的类加载器,第二个参数是获取到的接口数组,第三个是InvocationHandler(动态代理JDK)接口并需要实现该接口的invoke方法。invoke方法的第一个参数是代理对象(很少用),第二个参数是被代理对象执行的方法,第三个参数是被执行代理对象参入的参数。返回的是该代理对象
public <T> T getMapper(Class<?> mapperClass) {
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
return (T) proxyInstance;
}
5.Executor
Excetor是最后执行sql的类该类分为CacheExcetor和BaseExcetor两个实现类,和三个具体执行类(默认是SimpleExcetor)。CacheExcetor是一二级缓存对象类,若开启了一二级缓存先执行该类,但最终都是执行BaseExcetor类的SimpleExcetor。该类的select方法最终都是执行selectList方法。而增删改执行的都是update方法,因为jdbc底层增删改都是执行ExcuteUpdate方法。所以在update标签中写insert方法也可以
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
6.缓存
⼆级缓存构建在⼀级缓存之上,在收到查询请求时,MyBatis ⾸先会查询⼆级缓存,若⼆级缓存未命中,再去查询⼀级缓存,⼀级缓存没有,再查询数据库。⼆级缓存------》 ⼀级缓存------》数据库与⼀级缓存不同,⼆级缓存和具体的命名空间绑定,⼀个Mapper中有⼀个Cache,相同Mapper中的MappedStatement共⽤⼀个Cache,⼀级缓存则是和 SqlSession 绑定。
⼆级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。除此之外,若不加以控制,多个事务共⽤⼀个缓存实例,会导致脏读问题。⼆级缓存实现了Sqlsession之间的缓存数据共享,属于namespace级别
⼆级缓存具有丰富的缓存策略。⼆级缓存可由多个装饰器,与基础缓存组合⽽成⼆级缓存⼯作由 ⼀个缓存装饰执⾏器CachingExecutor和 ⼀个事务型预缓存TransactionalCache完成。
@Override
public void commit(boolean force) {
try {
// 主要是这句
executor.commit(isCommitOrRollbackRequired(force));
⼆级缓存的刷新
我们来看看SqlSession的更新操作
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction.
Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// CachingExecutor.commit()
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();// 在这⾥
}
// TransactionalCacheManager.commit()
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();// 在这⾥
}
}
// TransactionalCache.commit()
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();//这⼀句
reset();
}
// TransactionalCache.flushPendingEntries()
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
// 在这⾥真正的将entriesToAddOnCommit的对象逐个添加到delegate中,只有这时,⼆
级缓存才真正的⽣效
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}