分别创建两个maven-archetype-webapp项目。将分别其命名为javaweb_maven与jfinal_maven。在Javaweb_maven项目中,通过maven引入servlet(servlet和jsp的使用)、junit(单元测试)、mysql(数据库引擎)、jstl(JSP标准标签库)和taglibs(JSP标准标签库)。
在JFinal项目中,通过maven引入上面项目中所需要引入的所有外,需要额外引入jfinal(JFinal的使用)。
web.xml版本为3.0。
使用JFinal前需要先创建一个继承JFinalConfig的类(以下简称Config类),然后开始配置web.xml文件,在其中加上
<filter>
<filter-name>jfinal</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<init-param>
<param-name>configClass</param-name>
<param-value>config.Config</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
其中<param-value></param-value>项中填Config类的路径,<param-name></param-name>项中填继承Config类的名称。
一、连接数据库
事先准备:创建一个数据库配置文件:dbconfig.properties,里面写入数据库用到的各项配置,如:url,username,password,driverClass。然后将该文件复制两份放入两个项目的src/main/resources中。(注:放入两个项目中的该配置文件中的url后面的数据库名是不同的,记得修改。)
例,其中jfinal_test的dbconfig.properties文件代码:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:20001/jfinal_test
user=root
password=123456
1、JavaWeb
在JavaWeb中,我建立了一个DBCon类,放在jdbc包下,用来连接数据库,获取连接完数据库的Connection对象(后续与数据库相关操作都是通过Connection对象进行)。
在该类中,我定义了4个私有静态变量,并定义了一个静态块用来从数据库配置文件中读取出来的各项值,然后赋值给那4个私有变量。
private static String DB_DRIVER;
private static String DB_URL;
private static String DB_USERNAME;
private static String DB_PASSWORD;
static{
Properties p = new Properties();
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("dbconfig.properties");
try {
p.load(is);
DB_DRIVER = p.getProperty("driver");
DB_URL = p.getProperty("url");
DB_USERNAME = p.getProperty("user");
DB_PASSWORD = p.getProperty("password");
} catch (IOException e) {
e.printStackTrace();
}
}
在该类中,还定义了一个静态方法,用来获取Connection对象。
private static Connection connection;
public static Connection GetConnection(){
if (connection == null) {
try {
Class.forName(DB_DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
connection = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
之后其他类,就可以通过DBCon.GetConnection()来获取与数据库的连接对象了。
为了验证该方法是否正确,使用JUnit创建一个测试类DBConTest。里面添加一个测试方法testGetConnection,用来测试是否能成功获取数据库连接。
@Test
public void testGetConnection() {
Connection conn = DBCon.GetConnection();
try {
assertNotNull(conn.createStatement().executeQuery("select * from commodity"));
} catch (SQLException e) {
e.printStackTrace();
}
}
测试通过,说明该方法可以成功获取数据库的连接。(数据库里已经有表格,没有数据也能测试通过)
2、JFinal
在Config类中的configConstant方法中,使用loadPropertyFile方法获取数据库配置文件,这样方便之后再其他地方的调用。(me.setDevMode(true),是由于当前未开发版本,设置这个可以使得JFinal会对每次请求输出报告,如输出本次请求的URL、Controller、Method以及请求所携带的参数。)
@Override
public void configConstant(Constants me) {
loadPropertyFile("dbconfig.properties");
me.setDevMode(true);
}
设置完configConstant方法后,在Config类中的configPlugin方法里配置数据库的连接。
@Override
public void configPlugin(Plugins me) {
DruidPlugin dp = new DruidPlugin(getProperty("url"), getProperty("user"), getProperty("password"), getProperty("driver"));
me.add(dp);
ActiveRecordPlugin arp = new ActiveRecordPlugin(dp);
me.add(arp);
arp.addMapping("commodity", Commodity.class);
arp.addMapping("account", Account.class);
}
其中getProperty方法,读取的便是之前loadPropertyFile加载的配置文件中的相应键对应的值。如果存在重复的键值对,则取最后出现的键值对的值。
其中的arp.addMapping("commodity", Commodity.class)与arp.addMapping("account", Account.class),是完成了数据库表格与项目model的映射。Commodity.class与Account.class是在项目中创建的继承自Model<Commodity>与Model<Account>的类。
其中的DruidPlugin 类,这是一个数据源插件,可以使用jfinal自带的com.jfinal.plugin.druid.DruidPlugin类,不过需要在Maven中引入alibaba的druid项目,然后通过调用其构造函数,输入url、username、password和driver的值即可。除了使用jfinal自带的com.jfinal.plugin.druid.DruidPlugin类,还可以使用自定义的类,只需要实现com.jfinal.plugin.IPlugin接口和com.jfinal.plugin.activerecord.IDataSourceProvider接口即可,这样便无需在Maven中额外引入druid。
接下来是ActiveRecordPlugin这个类。其定义了一个接受IDataSourceProvider类型的构造器:
public ActiveRecordPlugin(IDataSourceProvider dataSourceProvider) {
this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);
}
this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider)这段代码读取了一个重载的构造器,跟踪进去后发现,那个构造器又调用了一个新的重载的构造器。下面为新的重载的构造器:
public ActiveRecordPlugin(String configName, IDataSourceProvider dataSourceProvider, int transactionLevel) {
if (StrKit.isBlank(configName)) {
throw new IllegalArgumentException("configName can not be blank");
}
if (dataSourceProvider == null) {
throw new IllegalArgumentException("dataSourceProvider can not be null");
}
this.dataSourceProvider = dataSourceProvider;
this.config = new Config(configName, null, transactionLevel);
}
此时,ActiveRecordPlugin的static变量的dataSourceProvider,就已经被赋为之前传入进来的实现了IDataSourceProvider接口的类型的实例了。
接下来看数据库到项目的映射的方法:
arp.addMapping("commodity", Commodity.class);
其中Commodity.class为:
public class Commodity extends Model<Commodity> {
private static final long serialVersionUID = -839562708492767863L;
public static final Commodity dao = new Commodity().dao();
}
其中声明的dao静态对象是为了方便查询操作而定义的,该对象并不是必须的。
回到ActiveRecordPlugin中,查看其是如何完成映射的。查看源码,我们可以发现,在ActiveRecordPlugin中,定义了两个addMapping方法:
public ActiveRecordPlugin addMapping(String tableName, String primaryKey, Class<? extends Model<?>> modelClass) {
tableList.add(new Table(tableName, primaryKey, modelClass));
return this;
}
public ActiveRecordPlugin addMapping(String tableName, Class<? extends Model<?>> modelClass) {
tableList.add(new Table(tableName, modelClass));
return this;
}
一个的参数中需要primaryKey,一个的参数中不需要primaryKey。在继续进入Table类中,查看调用的两个构造器有什么不同。
进去后可以发现,有primaryKey参数的构造器中,比没有primaryKey参数的构造器多使用了一个setPrimaryKey(primaryKey.trim())方法。而setPrimaryKey的方法具体内容如下:
void setPrimaryKey(String primaryKey) {
String[] arr = primaryKey.split(",");
for (int i=0; i<arr.length; i++)
arr[i] = arr[i].trim();
this.primaryKey = arr;
}
由此可知,是将传入的字符串,按“,”分割,去除空格后,将这个数组赋值给primaryKey。如果没有传入primaryKey的情况下,primaryKey的值默认为id。
因此通过addMapping方法来实现数据库表名到Model的映射关系的话,如果表的主键名为id的话,直接输入表名和相应的Model的类名即可;如果不是id的话,则需要手动指定。如果一个表有多个id的话,可以通过“,”分割开来。
回到源码中,在知道了如何使用数据库连接的情况下,再来看看具体的实现是如何进行的吧。
首先,我们将这个数据库连接是放在继承自JFinalConfig类的configPlugin方法中的。而这个Config类,我们是要在web.xml中配置的,如下。
<filter>
<filter-name>jfinal</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<init-param>
<param-name>configClass</param-name>
<param-value>config.Config</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
关于web.xml中的filter标签,是表示过滤器,其下方的filter-mapping标签中的<url-pattern>/*</url-pattern>则表明,通向该项目的任何请求,都会先转入该过滤器中,即必须先通过com.jfinal.core.JFinalFilter类。
JFinalFilter类继承自javax.servlet.Filter类。重写了init方法(Filter类的init方法会在服务器启动过程中调用),创建了Config的实例,并且调用了JFinal的init方法。具体代码如下:
private Handler handler;
private String encoding;
private JFinalConfig jfinalConfig;
private Constants constants;
private static final JFinal jfinal = JFinal.me();
private static Log log;
private int contextPathLength;
public void init(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());
}
其中的jfinal.init(jfinalConfig, filterConfig.getServletContext(),调用了JFinal的init方法:
boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {
this.servletContext = servletContext;
this.contextPath = servletContext.getContextPath();
initPathUtil();
Config.configJFinal(jfinalConfig); // start plugin, init log factory and init engine in this method
constants = Config.getConstants();
initActionMapping();
initHandler();
initRender();
initOreillyCos();
initTokenManager();
return true;
}
其中的Config.configJFinal(jfinalConfig)是一个关键点,主要是通过Config来加载暴露给程序员的核心文件。点击去看看:
static void configJFinal(JFinalConfig jfinalConfig) {
jfinalConfig.configConstant(constants); initLogFactory(); initEngine();
jfinalConfig.configRoute(routes);
jfinalConfig.configEngine(engine);
jfinalConfig.configPlugin(plugins); startPlugins(); // very important!!!
jfinalConfig.configInterceptor(interceptors);
jfinalConfig.configHandler(handlers);
}
注意其中的这一行:
jfinalConfig.configPlugin(plugins); startPlugins(); // very important!!!
这里的startPlugins();并不是注释,而是一个需要执行的方法。
而调用各个configxxxx方法的jfinalConfig对象,则是我们定义的Config对象的一个实例。因此这里的各个configxxxx方法,就是调用我们在Config类中定义的这几个方法。
在startPlugins这个方法中,就遍历了所有添加的Plugin,并分别调用了他们的start方法。这就是整个连接数据库的起始原点了。
而后回到ActiveRecordPlugin类中,查看其start()方法,在ActiveRecordPlugin的start()方法中,又调用了TableBuilder类的build方法。
在build方法中,遍历了添加进来的各个Table对象,并为其建表。并在其中调用了自身的doBuild方法,进行相关映射。源码如下:
private void doBuild(Table table, Connection conn, Config config) throws SQLException {
table.setColumnTypeMap(config.containerFactory.getAttrsMap());
if (table.getPrimaryKey() == null) {
table.setPrimaryKey(config.dialect.getDefaultPrimaryKey());
}
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);
Class<?> clazz = javaType.getType(colClassName);
if (clazz != null) {
table.setColumnType(colName, clazz);
}
else {
int type = rsmd.getColumnType(i);
if (type == Types.BINARY || type == Types.VARBINARY || 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();
}
3、小结
使用方式:
JavaWeb:
需要自己去从头开始写各个部分的代码,读取数据库配置文件,加载数据库驱动,获取数据库连接,之后有操作数据库操作的时候,再来调用这个获取的连接对象。
JFinal:
创建一个继承自JFinalConfig的类,并将其配置在web.xml中的jfinal过滤器中的初始化参数中(这是JFina初始化的操作,没有这一步JFinal将无法调用)。
之后在创建的Config类中的configPlugin方法里进行配置即可,示例代码如下:
@Override
public void configPlugin(Plugins me) {
loadPropertyFile("dbconfig.properties");
DruidPlugin dp = new DruidPlugin(getProperty("url"), getProperty("user"), getProperty("password"), getProperty("driver"));
me.add(dp);
ActiveRecordPlugin arp = new ActiveRecordPlugin(dp);
me.add(arp);
arp.addMapping("commodity", Commodity.class);
arp.addMapping("account", Account.class);
}
loadPropertyFile方法为读取相应的配置文件,之后可以通过getProperty方法取出配置文件中定义的各项值出来。
DruidPlugin 为数据库连接池插件,JFinal自带,不过需要引入alibaba的druid类库才可以使用。不过也可以不使用DruidPlugin,而使用一个自定义的类,不过需要实现IPlugin,IDataSourceProvider这两个接口。
me.add(IPlugin plugin)方法,添加一个实现了IPlugin接口的类型的对象进入pluginList中,而在项目部署过程中,会遍历pluginList中的所有对象,调用其start()方法。
ActiveRecordPlugin类为初始化数据库表格到项目Model的映射,使用arp.addMapping方法,传入“表格名”和“Model类名”,即可完成映射,不过这样完成的映射,表格的主键必须为id,如果表格的主键不是id,可以通过arp.addMapping(String tableName, String primaryKey, Class<? extends Model<?>> modelClass),即在添加一个String类型的参数位于第二的位置表示主键名即可。
JFinal启动类图示意: