java activerecord.db_JFinal 源码分析 [DB+ActiveRecord]

逑翰翮
2023-12-01

我记得以前有人跟我说,“面试的时候要看spring的源码,要看ioc、aop的源码"那为什么要看这些开源框架的源码呢,其实很多人都是"应急式"的去读,就像读一篇文章一下,用最快的速度把文章从头到尾读一遍,那结果就是当你读完它,你也不清楚它讲了一个什么故事,想表达什么。

一个优秀的架构的源码我认为就好像一本名著一样,你的“文学”水平越高,你就越能读出作者设计的精妙之处。一篇源码在你不同水平的时候,能读出不同的东西,因此,我觉得优秀的框架的源码是经久不衰的,反复读多少次都不嫌多,直到你能设计出预期并驾齐驱甚至超越它的优美的架构。

读源码起初是一件很痛苦的事儿,想赶紧把它像流水账一样的读完;慢慢实力增强后,会感觉到读源码能够不费力气的读通;再假以时日,就能看出这些精妙的设计模式的组合。我有一个朋友,典型的源码痴狂症,他跟我说他第一次看见spring的源码,感觉特别兴奋,读了一宿没睡觉.......好吧,我还有很长的路需要走~

话说多了,我们赶紧入正题:

JFinal的框架我24号的一篇博文写到过,它优秀的地方在精简代码上,那么有两处源码是我觉得是值得我们要好好解析一下,一处是初始化加载—servlet跳转,另一处是DB+ActiveRecord的映射。

那么DB映射相对比较简单,我们这次就先来看看。

首先我们看看代码,还是之前我写过的 dog与cat的故事。

//采用DB+ActiveRecord模式

ActiveRecordPlugin arp = newActiveRecordPlugin(c3p0Plugin);

me.add(arp);//进行DB映射

arp.addMapping("animal", AnimalModel.class);

第一步:为ActiveRecordPlugin的 private IDataSourceProvider dataSourceProvider 赋值。这三行代码就是加载DB映射的关键,那么我们复习一下,JFinal的DB映射无需配置文件,无需与DB对应的POJO,只需要写一个类,继承Model即可。

那么我们先来看看ActiveRecordPlugin的构造器。

publicActiveRecordPlugin(IDataSourceProvider dataSourceProvider) {this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);

}

这里重要的是dataSourceProvider,IDataSourceProvider是一个接口,它的运行时类型是

JFinal 源码分析 [DB+ActiveRecord]

那么,可以看到

this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);

这段代码又继续读取另一个重载的构造器,然后调用了

public ActiveRecordPlugin(String configName, IDataSourceProvider dataSourceProvider, inttransactionLevel) {if(StrKit.isBlank(configName))throw new IllegalArgumentException("configName can not be blank");if (dataSourceProvider == null)throw new IllegalArgumentException("dataSourceProvider can not be null");this.configName =configName.trim();this.dataSourceProvider =dataSourceProvider;this.setTransactionLevel(transactionLevel);

}

最重要的就是这行代码: this.dataSourceProvider = dataSourceProvider;

这时,ActiveRecordPlugin的static变量的dataSourceProvider就已经被赋为C3p0Plugin的实例了。

第二步:定义映射用POJO

public class AnimalModel extends Model {...}

这里Model的源码我们一会再看,现在不着急。

然后进行映射

//进行DB映射

arp.addMapping("animal", AnimalModel.class);

这里我们又回到了ActiveRecordPlugin类里,它实际上有两个addMapping方法,只是参数不同。

public ActiveRecordPlugin addMapping(String tableName, String primaryKey, Class extends Model>>modelClass) {

tableList.add(newTable(tableName, primaryKey, modelClass));return this;

}public ActiveRecordPlugin addMapping(String tableName, Class extends Model>>modelClass) {

tableList.add(newTable(tableName, modelClass));return this;

}

我们看到,第一个方法多了一个参数 String primaryKey,我的代码里用的是第二个方法。这两个方法实际上都调用了tableList.add(Table tbl)方法,我们看看tableList是什么

private List

它是ActiveRecordPlugin的一个成员变量,并且是private的,那我们可以猜到,tableList保存了所有的映射关系。(ActiveRecordPlugin真是强大,后面会越来越强大~)。

第三步:创建映射关系

newTable(tableName, primaryKey, modelClass)new Table(tableName, modelClass)

我们进去看看

public Table(String name, Class extends Model>>modelClass) {if(StrKit.isBlank(name))throw new IllegalArgumentException("Table name can not be blank.");if (modelClass == null)throw new IllegalArgumentException("Model class can not be null.");this.name =name.trim();this.modelClass =modelClass;

}public Table(String name, String primaryKey, Class extends Model>>modelClass) {if(StrKit.isBlank(name))throw new IllegalArgumentException("Table name can not be blank.");if(StrKit.isBlank(primaryKey))throw new IllegalArgumentException("Primary key can not be blank.");if (modelClass == null)throw new IllegalArgumentException("Model class can not be null.");this.name =name.trim();

setPrimaryKey(primaryKey.trim());//this.primaryKey = primaryKey.trim();

this.modelClass =modelClass;

}

这两个方法都是为Table里的成员变量赋值,第二个方法,也就是带primaryKey参数的那个多出一行,我们看看这一行干了什么

setPrimaryKey(primaryKey.trim()); //this.primaryKey = primaryKey.trim();

voidsetPrimaryKey(String primaryKey) {

String[] keyArr= primaryKey.split(",");if (keyArr.length > 1) {if (StrKit.isBlank(keyArr[0]) || StrKit.isBlank(keyArr[1]))throw new IllegalArgumentException("The composite primary key can not be blank.");this.primaryKey = keyArr[0].trim();this.secondaryKey = keyArr[1].trim();

}else{this.primaryKey =primaryKey;

}

这样的作用就是为Table下的primaryKey 和 secondaryKey赋值。

第四步:加载ActiveRecordPlugin

那么代码好像跟到这里就完事了,怎么回事?是不是跟丢了?

别忘了,ActiveRecordPlugin是在FinalConfig里的configPlugin方法加载的。那么又有谁来加载FinalConfig呢?

PS:(FinalConfig是我自己定义的类)

这儿涉及到初始化的加载了,我简单的讲一下。

public class FinalConfig extends JFinalConfig

整个JFinal的入口是web.xml的一段配置:

jfinal

com.jfinal.core.JFinalFilter

configClass

com.demo.config.FinalConfig

接着我们看到了关键的累 JFinalFilter,还是点进去看看。

public final class JFinalFilter implements Filter

这个类实现了Filter接口,那就得实现方法init(),doFilter(),destroy()方法。

我们去看init()方法:

public voidinit(FilterConfig filterConfig) throws ServletException {

createJFinalConfig(filterConfig.getInitParameter("configClass"));if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false)throw new RuntimeException("JFinal init error!");

handler=jfinal.getHandler();

constants=Config.getConstants();

encoding=constants.getEncoding();

jfinalConfig.afterJFinalStart();

String contextPath=filterConfig.getServletContext().getContextPath();

contextPathLength= (contextPath == null || "/".equals(contextPath) ? 0: contextPath.length());

}

绕过其他的加载,直接看这行

if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false)

我们看看jfinal的类型是 private static final JFinal jfinal = JFinal.me();

那么我们去JFinal类里看看它的init方法。

boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {this.servletContext =servletContext;this.contextPath =servletContext.getContextPath();

initPathUtil();

Config.configJFinal(jfinalConfig);//start plugin and init logger factory in this method

constants =Config.getConstants();

initActionMapping();

initHandler();

initRender();

initOreillyCos();

initI18n();

initTokenManager();return true;

看这行,下面这行主要是通过Config来加载暴露给程序员的核心文件,JFinalConfig的子类FinalConfig。

Config.configJFinal(jfinalConfig); //start plugin and init logger factory in this method

再点进去

*Config order: constant, route, plugin, interceptor, handler*/taticvoidconfigJFinal(JFinalConfig jfinalConfig) {

jfinalConfig.configConstant(constants); initLoggerFactory();

jfinalConfig.configRoute(routes);

jfinalConfig.configPlugin(plugins); startPlugins();//very important!!!

jfinalConfig.configInterceptor(interceptors);

jfinalConfig.configHandler(handlers);

这段代码实际上有个地方特别坑!就是

jfinalConfig.configPlugin(plugins); startPlugins(); //very important!!!

这行代码一共做了两件事,第一件事是jfinalConfig.configPlugin(plugins);来加载插件。还记得我们之前写的FinalConfig里的configPlugin(Plugins me) 方法吗?

/**

* Config plugin

* 配置插件

* JFinal有自己独创的 DB + ActiveRecord模式

* 此处需要导入ActiveRecord插件*/@Overridepublic voidconfigPlugin(Plugins me) {//读取db配置文件

loadPropertyFile("db.properties");//采用c3p0数据源

C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcUrl"),getProperty("user"), getProperty("password"));

me.add(c3p0Plugin);//采用DB+ActiveRecord模式

ActiveRecordPlugin arp = newActiveRecordPlugin(c3p0Plugin);

me.add(arp);//进行DB映射

arp.addMapping("animal", AnimalModel.class);

}

它实际上就是通过me.add来加载插件,通过Config的 private static final Plugins plugins = new Plugins(); 来装载。

第二件事就是 发现没有,后面的startPlugins()不是注释!是一个方法,这块实在太坑了,恰巧,这就是我们要找到的地方。

这个方法的代码有点长,但因为很重要,我不得不都贴出来。

private static voidstartPlugins() {

List pluginList =plugins.getPluginList();if (pluginList != null) {for(IPlugin plugin : pluginList) {try{//process ActiveRecordPlugin devMode

if(plugin instanceof com.jfinal.plugin.activerecord.ActiveRecordPlugin) {

com.jfinal.plugin.activerecord.ActiveRecordPlugin arp=(com.jfinal.plugin.activerecord.ActiveRecordPlugin)plugin;if (arp.getDevMode() == null)

arp.setDevMode(constants.getDevMode());

}

boolean success=plugin.start();if (!success) {

String message= "Plugin start error:" +plugin.getClass().getName();

log.error(message);throw newRuntimeException(message);

}

}catch(Exception e) {

String message=

"Plugin start error:" + plugin.getClass().getName() + ". \n" +e.getMessage();

log.error(message, e);throw newRuntimeException(message, e);

}

}

}

}

上面这个方法一共有两个地方要注意一下,

for (IPlugin plugin : pluginList) {

上面这行是循环所有的插件,并且启动插件的start()方法。

那么,我们中有一个插件记不记得是ActiveRecordPlugin的实例?那么

boolean success = plugin.start();

这行代码就会执行ActiveRecordPlugin下的start()代码。终于绕回来了!!红军二万五千里长征,为了证明这个调用,我写了多少字....

那么我们看ActiveRecordPlugin下的start()方法吧,实际上这个start()方法是因为实现了IPlugin接口里的start()方法。

publicboolean start() {if(isStarted)return true;if (dataSourceProvider != null)

dataSource=dataSourceProvider.getDataSource();if (dataSource == null)throw new RuntimeException("ActiveRecord start error:

ActiveRecordPlugin need DataSource or DataSourceProvider");

if (config == null)

config= newConfig(configName, dataSource, dialect,

showSql, devMode, transactionLevel, containerFactory, cache);

DbKit.addConfig(config);

boolean succeed=TableBuilder.build(tableList, config);if(succeed) {

Db.init();

isStarted= true;

}returnsucceed;

}

我们直接看与DB映射有关的代码,首先是取得dataSource,dataSourceProvider这个忘了没,忘了就翻到最前面,第一步讲的。

config = new Config(configName, dataSource, dialect, showSql, devMode, transactionLevel, containerFactory, cache);

这行代码中的dataSource 在插件里配置的C3P0数据源。这里的Config与前面加载FinalConfig的可不是一个啊,千万别看错了,这个是DB的 com.jfinal.plugin.activerecord.Config。

第五步:TableBuilder

来自ActiveRecordPlugin.java

boolean succeed = TableBuilder.build(tableList, config);

static boolean build(List

Table temp= null;

Connection conn= null;try{

conn=config.dataSource.getConnection();

TableMapping tableMapping=TableMapping.me();for(Table table : tableList) {

temp=table;

doBuild(table, conn, config);

tableMapping.putTable(table);

DbKit.addModelToConfigMapping(table.getModelClass(), config);

}return true;

}catch(Exception e) {if (temp != null)

System.err.println("Can not create Table object,

maybe the table "+ temp.getName() +" is not exists.");

throw newActiveRecordException(e);

}finally{

config.close(conn);

}

}

这里循环所有的tableList,对每个Table对象进行建表。那么我们先看看Table是用什么来存储数据库映射关系的,相信大家都能猜到是Map了。

public classTable {privateString name;privateString primaryKey;private String secondaryKey = null;private Map> columnTypeMap; //config.containerFactory.getAttrsMap();

private Class extends Model>> modelClass;

columnTypeMap是关键字段,暂且记下来。

下面我们还是回到TableBuilder里的doBuild(table, conn, config);方法。

这个才是DB映射的关键,我其实直接讲这一个类就可以的......这个方法代码实在太多了,我贴部分代码做讲解吧。

那么第六步:doBuild详解。

这块有点类,我直接在代码里写注释吧:

@SuppressWarnings("unchecked")private static voiddoBuild(Table table, Connection conn, Config config) throws SQLException {//初始化 Table 里的columnTypeMap字段。

table.setColumnTypeMap(config.containerFactory.getAttrsMap());//取得主键,如果取不到的话,默认设置"id"。//记不记得最开始的两个同名不同参的方法 addMapping(...),

在这才体现出后续处理的不同。if (table.getPrimaryKey() == null)

table.setPrimaryKey(config.dialect.getDefaultPrimaryKey());//此处如果没有设置方言,则默认 Dialect dialect = new MysqlDialect(); Mysql的方言。//sql为"select * from `" + tableName + "` where 1 = 2";

String sql =config.dialect.forTableBuilderDoBuild(table.getName());

Statement stm=conn.createStatement();

ResultSet rs=stm.executeQuery(sql);//取得个字段的信息

ResultSetMetaData rsmd =rs.getMetaData();//匹配映射

for (int i=1; i<=rsmd.getColumnCount(); i++) {

String colName=rsmd.getColumnName(i);

String colClassName=rsmd.getColumnClassName(i);if ("java.lang.String".equals(colClassName)) {//varchar, char, enum, set, text, tinytext, mediumtext, longtext

table.setColumnType(colName, String.class);

}else if ("java.lang.Integer".equals(colClassName)) {//int, integer, tinyint, smallint, mediumint

table.setColumnType(colName, Integer.class);

}else if ("java.lang.Long".equals(colClassName)) {//bigint

table.setColumnType(colName, Long.class);

}//else if ("java.util.Date".equals(colClassName)) {//java.util.Data can not be returned//java.sql.Date, java.sql.Time,

java.sql.Timestamp all extends java.util.Data so getDate can returnthe three types data//result.addInfo(colName, java.util.Date.class);//}

else if ("java.sql.Date".equals(colClassName)) {//date, year

table.setColumnType(colName, java.sql.Date.class);

}else if ("java.lang.Double".equals(colClassName)) {//real, double

table.setColumnType(colName, Double.class);

}else if ("java.lang.Float".equals(colClassName)) {//float

table.setColumnType(colName, Float.class);

}else if ("java.lang.Boolean".equals(colClassName)) {//bit

table.setColumnType(colName, Boolean.class);

}else if ("java.sql.Time".equals(colClassName)) {//time

table.setColumnType(colName, java.sql.Time.class);

}else if ("java.sql.Timestamp".equals(colClassName)) {//timestamp, datetime

table.setColumnType(colName, java.sql.Timestamp.class);

}else if ("java.math.BigDecimal".equals(colClassName)) {//decimal, numeric

table.setColumnType(colName, java.math.BigDecimal.class);

}else if ("[B".equals(colClassName)) {//binary, varbinary, tinyblob, blob, mediumblob, longblob//qjd project: print_info.content varbinary(61800);

table.setColumnType(colName, byte[].class);

}else{int type =rsmd.getColumnType(i);if (type ==Types.BLOB) {

table.setColumnType(colName,byte[].class);

}else if (type == Types.CLOB || type ==Types.NCLOB) {

table.setColumnType(colName, String.class);

}else{

table.setColumnType(colName, String.class);

}//core.TypeConverter//throw new RuntimeException

("You've got new type to mapping. Please add code in" + TableBuilder.class.getName()+ ". The ColumnClassName can't be mapped:" +colClassName);

}

}

rs.close();

stm.close();

}

这里巧妙的运用了 where 1=2的无检索条件结果,通过ResultSetMetaData rsmd = rs.getMetaData(); 导出了DB模型,这招确实漂亮。之前我还冥思苦相,他是怎么做的呢,看着此处源码,茅塞顿开。

接着,把编辑好的Table实例,放到TableMapping的成员变量 Model>>, Table> modelToTableMap 里去,TableMapping是单例的。

private final Map>, Table> modelToTableMap=

new HashMap>, Table>();

public voidputTable(Table table) {

modelToTableMap.put(table.getModelClass(), table);

}

这样,所有的映射关系就都存在TableMapping的modelToTableMap

tableMapping.putTable(table);

再将modelToConfig都放入DbKit.modelToConfig里。

DbKit.addModelToConfigMapping(table.getModelClass(), config);

第七步,使用

Model里的save方法举例:

/**

* Save model.*/

publicboolean save() {

Config config=getConfig();

Table table=getTable();

StringBuilder sql= newStringBuilder();

List paras = new ArrayList();

config.dialect.forModelSave(table, attrs, sql, paras);//if (paras.size() == 0) return false;//The sql "insert into tableName() values()" works fine, so delete this line//--------

Connection conn = null;

PreparedStatement pst= null;int result = 0;try{

conn=config.getConnection();if(config.dialect.isOracle())

pst=conn.prepareStatement(sql.toString(),newString[]{table.getPrimaryKey()});elsepst=conn.prepareStatement(sql.toString(),

Statement.RETURN_GENERATED_KEYS);

config.dialect.fillStatement(pst, paras);

result=pst.executeUpdate();

getGeneratedKey(pst, table);

getModifyFlag().clear();return result >= 1;

}catch(Exception e) {throw newActiveRecordException(e);

}finally{

config.close(pst, conn);

}

}

Config config = getConfig();

上面这行就是调用DbKit的方法,取得DB配置。

public static Config getConfig(Class extends Model>modelClass) {return modelToConfig.get(modelClass);

}

下面这段代码是去单例的TableMapping里取得表的具体信息。

Table table = getTable();

privateTable getTable() {returnTableMapping.me().getTable(getClass());

}

 类似资料: