[JBoss]Java的工作排程套件-Quartz

序言

作為一個Application Server,JBoss裡的應用程式開發起來主要都是被動的等待Client呼叫啟動。但實際應用上還是有可能需要有個程序能夠在特定時間執行某些命令,例如於午夜12點整進行資料的Reset這種工作。
在不依賴作業系統或是另外執行一支程式處裡工作的狀況,其實JBoss預設就有推薦使用的工作排程套件-Quartz
就Quartz的功能而言,有的功能是與Java內建的Timer重複的,但它功能更多更強大,所以相對的要做一些設定才能使用。
以下我就舉幾個我用的方法來一探Quartz的設定(當然,這不見得是最好的作法)。

開發環境

JBoss設定

JBoss內建就有Quartz的套件,但版本很老舊,從網路上得到的資訊來看,可能會有一些問題存在。
因此建議的作法是把內建的套件刪除掉,換上新的版本。
  1. 刪除Jboss下common\lib\目錄的quartz.jar檔
  2. 刪除Jboss下server發佈目錄下的quartz-ra.rar,如jboss\server\default\deploy目錄的quartz-ra.rar
  3. quartz套件的壓縮檔打開,將裡面的quartz-all-1.7.3.jar檔案複製到jboss\common\lib(或jboss\server\default\lib)

Quartz設定檔

Quartz 提供了兩種不同的方式用來把排程工作存在記憶體(RAMJobStore)或資料庫(JDBCJobStore。)中。
  • RAMJobStore:是預設的方法,因為所有資料都存在記憶體中,所以速度最快,但是如果伺服器停止後,所有排程的工作都會消失
  • JDBCJobStore:透過 JDBC 把所有工作排成資訊存在資料庫中,但代價就是效能降低和設定複雜度的提高(尤其是有參數要透過JobDataMap在工作執行時使用時)。
在這邊我使用最簡單的RAMJobStore來進行工作排程。
這時會需要建立一個設定檔【quartz-service.xml】來設定工作排程套件的參數。這個檔案會放在JBoss的發佈目錄下,如jboss\server\default\deploy
<?xml version="1.0" encoding="UTF-8"?>

<server>

<mbean code="org.quartz.ee.jmx.jboss.QuartzService" name="user:service=QuartzService,name=QuartzService">
<attribute name="JndiName">Quartz</attribute>
<attribute name="StartScheduler">true</attribute>
<attribute name="Properties">
org.quartz.scheduler.instanceName = DefaultQuartzScheduler
org.quartz.scheduler.rmi.export = false
org.quartz.scheduler.rmi.proxy = false
org.quartz.scheduler.xaTransacted = false

org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 5
org.quartz.threadPool.threadPriority = 4

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
</attribute>
</mbean>

</server>

工作類別

工作要執行的內容必須實作【org.quartz.Job】介面,如
package control;

import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class MyJob implements Job {
static int jobs=0;
@Override
public void execute(JobExecutionContext arg0) throws JobExecutionException {
JobDataMap dataMap = arg0.getJobDetail().getJobDataMap();
Object data1 =dataMap.get("data1");
Object data2 =dataMap.get("data2");
System.out.println("Start MyJob");
System.out.println("data1="+data1);
System.out.println("data2="+data2);
}

}

排程工作

排程的方式有兩種,一種是設定多久執行工作一次(SimpleTrigger),一種是設定什麼時間要執行工作一次(CronTrigger)。
兩者最主要差別在於描述方式。相同的是兩者都可以設定排程啟動時間,結束時間,重複次數等設定。
  • 使用SimpleTrigger
 String groupName = "Default";  //工作群組
String jobName = "MySimpleJob"; //工作名稱
InitialContext ctx;
ctx = new InitialContext();
Scheduler scheduler = (Scheduler) ctx.lookup("Quartz");
//如果工作已排程,則刪除同樣工作群組-工作名稱的工作
if (scheduler.getJobDetail(jobName, groupName) != null) {
scheduler.deleteJob(jobName, groupName);
}
JobDetail jobDetail = new JobDetail(jobName, groupName,MyJob.class); //設定工作執行的類別為MyJob
//設定要傳給工作的參數
jobDetail.getJobDataMap().put("data1", "Job1");
jobDetail.getJobDataMap().put("data2", jobName);
SimpleTrigger simpleTrigger = new SimpleTrigger(jobName, groupName); //Trigger也會設定群組與名稱,但我不確定是有什麼功用
simpleTrigger.setRepeatCount(-1); //設定-1無限次重複,如果設為0則就只會執行一次
simpleTrigger.setRepeatInterval(15*60*1000); //每15分鐘做一次

Calendar curCal = Calendar.getInstance();
curCal.add(Calendar.MINUTE, 10);
simpleTrigger.setStartTime(curCal.getTime()); //現在時間的10分鐘後啟動工作,如果不設定的話排程開始就會執行第一次

scheduler.scheduleJob(jobDetail, simpleTrigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
  • 使用CronTrigger
 String groupName = "Default";  //工作群組
String jobName = "MyCronJob"; //工作名稱
InitialContext ctx;
ctx = new InitialContext();
Scheduler scheduler = (Scheduler) ctx.lookup("Quartz");
//如果工作已排程,則刪除同樣工作群組-工作名稱的工作
if (scheduler.getJobDetail(jobName, groupName) != null) {
scheduler.deleteJob(jobName, groupName);
}
JobDetail jobDetail = new JobDetail(jobName, groupName,MyJob.class); //設定工作執行的類別為MyJob
//設定要傳給工作的參數
jobDetail.getJobDataMap().put("data1", "Job2");
jobDetail.getJobDataMap().put("data2", jobName);
CronTrigger trigger = new CronTrigger(jobName, groupName,"0 0 0 * * ?"); //每天0點整執行
scheduler.scheduleJob(jobDetail, trigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}

在應用程式第一次被呼叫時排程工作

在Java Web Application中目前我還沒找到方法能在Jboss啟動時呼叫某個function,只能想到兩種方式當有第一個人啟動這個網站中的網頁時進行第一次的初始設定。
第一種方法是在static instance建立時在被New的物件建構函式中寫要做的工作,像是下面的程式。
但這種做法最大壞處是你沒辦法餵給它關於Web的Session、ServletContext等資訊,因為他是在Web內容還沒建立起來時就產生的。
import java.util.Calendar;

import javax.naming.InitialContext;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SimpleTrigger;

public class InitJob {
private static InitJob instance=new InitJob();
private InitJob(){
try {
String groupName = "Default"; //工作群組
String jobName = "MySimpleJob"; //工作名稱
InitialContext ctx;
ctx = new InitialContext();
Scheduler scheduler = (Scheduler) ctx.lookup("Quartz");
//如果工作已排程,則刪除同樣工作群組-工作名稱的工作
if (scheduler.getJobDetail(jobName, groupName) != null) {
scheduler.deleteJob(jobName, groupName);
}
JobDetail jobDetail = new JobDetail(jobName, groupName,MyJob.class); //設定工作執行的類別為MyJob
//設定要傳給工作的參數
jobDetail.getJobDataMap().put("data1", "Job1");
jobDetail.getJobDataMap().put("data2", jobName);
SimpleTrigger simpleTrigger = new SimpleTrigger(jobName, groupName); //Trigger也會設定群組與名稱,但我不確定是有什麼功用
simpleTrigger.setRepeatCount(-1); //設定-1無限次重複,如果設為0則就只會執行一次
simpleTrigger.setRepeatInterval(15*60*1000); //每15分鐘做一次

Calendar curCal = Calendar.getInstance();
curCal.add(Calendar.MINUTE, 10);
simpleTrigger.setStartTime(curCal.getTime()); //現在時間的10分鐘後啟動工作,如果不設定的話排程開始就會執行第一次

scheduler.scheduleJob(jobDetail, simpleTrigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
第二種方法是在Session建立時,設定一個Static 布林變數來判斷是否是第一次啟動,然後在裡面執行要做的工作,像是下面的程式。但相對付出的代價就是每個人進來時都會判斷一次。
如果要用這種方法就要再專案的web.xml中增加listener標籤,如
<?xml version="1.0" encoding="UTF-8"?>
<web-app...>
...
<listener>
<listener-class>control.InitSession</listener-class>
</listener>
...
</web-app>
程式如下
package control;

import javax.naming.InitialContext;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;

public class InitSession implements HttpSessionListener {
static boolean isInited=false;
@Override
public void sessionCreated(HttpSessionEvent arg0) {
if(!isInited){
//可取得Web相關的資訊
ServletContext servletContext = arg0.getSession().getServletContext();
HttpSession session = arg0.getSession();
//工作設定
String groupName = "Default"; //工作群組
String jobName = "MyCronJob"; //工作名稱
InitialContext ctx;
ctx = new InitialContext();
Scheduler scheduler = (Scheduler) ctx.lookup("Quartz");
//如果工作已排程,則刪除同樣工作群組-工作名稱的工作
if (scheduler.getJobDetail(jobName, groupName) != null) {
scheduler.deleteJob(jobName, groupName);
}
JobDetail jobDetail = new JobDetail(jobName, groupName,MyJob.class); //設定工作執行的類別為MyJob
//設定要傳給工作的參數
jobDetail.getJobDataMap().put("data1", "Job2");
jobDetail.getJobDataMap().put("data2", jobName);
CronTrigger trigger = new CronTrigger(jobName, groupName,"0 0 0 * * ?"); //每天0點整執行
scheduler.scheduleJob(jobDetail, trigger);
if (!scheduler.isShutdown()) {
scheduler.start();
}
isInited=true;
}
}

@Override
public void sessionDestroyed(HttpSessionEvent arg0) {
}
}

總結

以上就是在JBoss上面我使用Quartz的使用經驗,至於是否有更好的作法或是我的作法有什麼缺陷,歡迎留言告訴我,謝謝

參考資料

[Java]MySQL/Oracle資料庫存取函式

序言

延續上一篇,這篇針對MySQL資料庫的存取寫出的函式提供給大家參考,開發環境與資料庫連接器的設定可參考上一篇。
我在這邊有兩種建立連線物件的方式,預設是來自JNDI的連線設定,另一種是直接產生的方式,可在建立物件時做選擇。
使用範例的部分我改用JUnit來進行我自己的原始碼驗證,大家可直接參考呼叫的方式。
PS.2011/6/21 更新,加入Oracle的連線方法(但分頁查詢的函式只能用在MySQL)

原始碼函式類別

package common;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

public class DBBaseIO {
private static boolean _isDebug=false;
public enum DBType{
MySQL
,Oracle
}

public static void setDebug(boolean isDebug) {
_isDebug = isDebug;
}

public static boolean isIsdebug() {
return _isDebug;
}

private Connection con = null; // Database objects
public Connection getConnection() {
return con;
}

private Statement stat = null;
private ResultSet rs = null;
private PreparedStatement pst = null;
private boolean isClosed=false;
public boolean isClosedConn(){
return isClosed;
}

protected void finalize () {
if(!isClosed)CloseConn();
}

/**
* 建立資料庫存取物件(使用JNDI XML設定,名稱為MySqlDS)
* @throws SQLException
* @throws NamingException
*/
public DBBaseIO() throws SQLException, NamingException {
setupConnMySQLByXML();
}
/**
* 建立資料庫存取物件(使用JNDI XML設定)
* @param dsName 設定名稱
* @throws SQLException
* @throws NamingException
*/
public DBBaseIO(String dsName) throws SQLException, NamingException {
setupConnMySQLByXML(dsName);
}
/**
*
* @param sourceType
* @throws ClassNotFoundException
* @throws SQLException
* @throws NamingException
*/
public DBBaseIO(int sourceType) throws ClassNotFoundException, SQLException, NamingException{
switch(sourceType){
case 0:
setupConnMySQL("localhost:3306", "root", "", "mysql");
break;
case 1:
setupConnMySQLByXML();
break;
}
}
/**
* 建立資料庫存取物件
*
* @param str_SQLIP
* 資料庫IP
* @param str_SQLID
* 登入帳號
* @param str_SQLPSWD
* 登入密碼
* @param str_SQLDefaultDBName
* 預設資料庫
* @throws SQLException
* @throws ClassNotFoundException
*/
public DBBaseIO(String str_SQLIP, String str_SQLID, String str_SQLPSWD,
String str_SQLDefaultDBName) throws ClassNotFoundException, SQLException {
setupConnMySQL(str_SQLIP, str_SQLID, str_SQLPSWD, str_SQLDefaultDBName);
}
/**
* 建立資料庫存取物件
*
* @param dbtype
* 資料庫類型
* @param str_SQLIP
* 資料庫IP
* @param str_SQLID
* 登入帳號
* @param str_SQLPSWD
* 登入密碼
* @param str_SQLDefaultDBName
* 預設資料庫
* @throws SQLException
* @throws ClassNotFoundException
*/
public DBBaseIO(DBType dbtype,String str_SQLIP, String str_SQLID, String str_SQLPSWD,
String str_SQLDefaultDBName) throws ClassNotFoundException, SQLException {
if(dbtype==DBType.MySQL)
setupConnMySQL(str_SQLIP, str_SQLID, str_SQLPSWD, str_SQLDefaultDBName);
else if(dbtype==DBType.Oracle)
setupConnOracle(str_SQLIP, str_SQLID, str_SQLPSWD, str_SQLDefaultDBName);
else
throw new ClassNotFoundException("DBType un-define");
}
private void setupConnMySQLByXML() throws NamingException, SQLException{setupConnMySQLByXML("MySqlDS"); }
private void setupConnMySQLByXML(String name) throws NamingException, SQLException{
if(isIsdebug()){
System.out.println("DataSource:"+"java:/" + name);
}

try{
InitialContext ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup("java:/" + name);
con = ds.getConnection();
} catch (NamingException e) {
System.err.println("Connection XML Load Error :" + e.toString());
throw e;
} catch (SQLException e) {
System.err.println("Get Connection Error :" + e.toString());
throw e;
}
}

private void setupConnMySQL(String str_SQLIP, String str_SQLID,
String str_SQLPSWD, String str_SQLDefaultDBName) throws ClassNotFoundException,SQLException {
if(isIsdebug()){
System.out.println("DataSource:"+"jdbc:mysql://" + str_SQLIP + "/"
+ str_SQLDefaultDBName
+ "?useUnicode=true&characterEncoding=UTF8");
System.out.println("ID:"+str_SQLID+"\tPassword:"+str_SQLPSWD);
System.out.println("JDBC Driver:" + "com.mysql.jdbc.Driver");
}
try {
Class.forName("com.mysql.jdbc.Driver");

con = DriverManager.getConnection("jdbc:mysql://" + str_SQLIP + "/"
+ str_SQLDefaultDBName
+ "?useUnicode=true&characterEncoding=UTF8", str_SQLID,
str_SQLPSWD);

} catch (ClassNotFoundException e) {
System.err.println("DriverClassNotFound :" + e.toString());
throw e;
}
catch (SQLException x) {
System.err.println("Exception :" + x.toString());
throw x;
}
}
private void setupConnOracle(String str_SQLIP, String str_SQLID,
String str_SQLPSWD, String str_SQLDefaultDBName) throws ClassNotFoundException,SQLException {
if(isIsdebug()){
System.out.println("DataSource:"+"jdbc:oracle:thin:"+str_SQLID+"/"+str_SQLPSWD+"@//" + str_SQLIP + "/"
+ str_SQLDefaultDBName);
}
try {
Class.forName("oracle.jdbc.OracleDriver");

con = DriverManager.getConnection("jdbc:oracle:thin:"+str_SQLID+"/"+str_SQLPSWD+"@//" + str_SQLIP + "/"
+ str_SQLDefaultDBName);

} catch (ClassNotFoundException e) {
System.err.println("DriverClassNotFound :" + e.toString());
throw e;
}
catch (SQLException x) {
System.err.println("Exception :" + x.toString());
throw x;
}
}

/**
* 執行insert update delete等更新指令用
*
* @param str_SQL
* 查詢語法
* @return 資料庫變動筆數
*/
public int executeUpdate(String str_SQL) throws SQLException,NullPointerException {
return executeUpdate(str_SQL, null);
}

/**
* 執行insert update delete等更新指令用
*
* @param str_SQL
* 查詢語法
* @param param
* 參數設定,SQL語法中以?指定,順序即為?的順序,如 where name=? (此參數可為null)
* @return 資料庫變動筆數
*/
public int executeUpdate(String str_SQL, Object[] param) throws SQLException ,NullPointerException{
return executeUpdate(str_SQL, param, true);
}

/**
* 執行insert update delete等更新指令用
*
* @param str_SQL
* 查詢語法
* @param param
* 參數設定,SQL語法中以?指定,順序即為?的順序,如 where name=? (此參數可為null)
* @param isCloseConn
* 是否關閉連線
* @return 資料庫變動筆數
*/
public int executeUpdate(String str_SQL, Object[] param, boolean isCloseConn) throws SQLException,NullPointerException {
if(isIsdebug()){
System.out.println("executeUpdate:"+str_SQL);
if(param!=null)
for(int i=0;i<param.length;i++)
System.out.println("Parameter "+ i +":"+param[i]);
System.out.println("isCloseConn :"+isCloseConn);
}

int result = 0;
if (con != null) {
try {
int i = 0;
if (param == null) {
stat = con.createStatement();
result = stat.executeUpdate(str_SQL);
} else {
pst = con.prepareStatement(str_SQL);
for (i = 0; i < param.length; i++) {
pst.setObject(i + 1, param[i]);
}
result = pst.executeUpdate();
try {
pst.clearParameters();
} catch (Exception e) {
CloseConn();
}
}
} catch (SQLException e) {
System.err.println("executeUpdate Exception :" + e.toString());
throw e;
} finally {
CloseConn(isCloseConn);
}
} else {
System.err.println("Connection is null");
throw new NullPointerException("Connection is null");
}
return result;
}


/**
* 批次執行SQL指令使用
*
* @param str_SQL
* 查詢語法的陣列
* @param isCloseConn
* 是否關閉連線
* @return 資料庫變動筆數的陣列
*/
public int[] executeBatch(String[] str_SQL, boolean isCloseConn) throws SQLException,NullPointerException {
if(isIsdebug()){
System.out.println("executeBatch:"+str_SQL);
}

int[] result=null;
if (con != null) {
try {
int i = 0;
stat = con.createStatement();
for (i = 0; i < str_SQL.length; i++) {
stat.addBatch(str_SQL[i]);
}
result=stat.executeBatch();
if(isIsdebug()){
System.out.println("executeBatch result:");
for (i = 0; i < result.length; i++) {
System.out.println(i+":"+result[i]);
}
}
} catch (SQLException e) {
System.err.println("executeBatch Exception :" + e.toString());
throw e;
} finally {
CloseConn(isCloseConn);
}
} else {
System.err.println("Connection is null");
throw new NullPointerException("Connection is null");
}
return result;
}

/**
* 批次執行SQL指令使用
* @param str_SQL 查詢語法
* @param param 以相同數量的參數形成的ArrayList
* @param isCloseConn 是否關閉連線
* @return 資料庫變動筆數的陣列
* @throws SQLException
* @throws NullPointerException
*/
public int[] addBatch(String str_SQL, ArrayList<Object[]> param, boolean isCloseConn) throws SQLException,NullPointerException {

int i = 0;
int j = 0;
if(isIsdebug()){
System.out.println("executeBatch:"+str_SQL);
if(param!=null)
for (i = 0; i < param.size(); i++) {
for (j = 0;j < param.get(i).length; j++) {
System.out.println("Parameter "+ i+","+j +":"+param.get(i)[j]);
}
}
}

int[] result=null;
if (con != null) {
try {
pst = con.prepareStatement(str_SQL);
for (i = 0; i < param.size(); i++) {
for (j = 0;j < param.get(i).length; j++) {
pst.setObject(j + 1, param.get(i)[j]);
}
pst.addBatch();
}
result=pst.executeBatch();
if(isIsdebug()){
System.out.println("executeBatch result:");
for (i = 0; i < result.length; i++) {
System.out.println(i+":"+result[i]);
}
}
} catch (SQLException e) {
System.err.println("executeBatch Exception :" + e.toString());
throw e;
} finally {
CloseConn(isCloseConn);
}
} else {
System.err.println("Connection is null");
throw new NullPointerException("Connection is null");
}

return result;
}

/**
* 查詢資料
*
* @param str_SQL
* 查詢語法
* @return 每列一筆資料,每筆資料的欄位存於 Object Array中
*/
public ArrayList<Object[]> executeQuery(String str_SQL) throws SQLException,NullPointerException {
return executeQuery(str_SQL, null);
}
/**
* 查詢資料
*
* @param str_SQL
* 查詢語法
* @param param
* 參數設定,SQL語法中以?指定,順序即為?的順序,如 where name=? (此參數可為null)
* @return 每列一筆資料,每筆資料的欄位存於 Object Array中
*/
public ArrayList<Object[]> executeQuery(String str_SQL, Object[] param) throws SQLException,NullPointerException {
return executeQuery(str_SQL, param, true);
}

/**
* 查詢資料
*
* @param str_SQL
* 查詢語法
* @param param
* 參數設定,SQL語法中以?指定,順序即為?的順序,如 where name=? (此參數可為null)
* @param isCloseConn
* 是否關閉連線
* @return 每列一筆資料,每筆資料的欄位存於 Object Array中
*/
public ArrayList<Object[]> executeQuery(String str_SQL, Object[] param,
boolean isCloseConn) throws SQLException,NullPointerException {
if(isIsdebug()){
System.out.println("executeQuery:"+str_SQL);
if(param!=null)
for(int i=0;i<param.length;i++)
System.out.println("Parameter "+ i +":"+param[i]);
System.out.println("isCloseConn :"+isCloseConn);
}
ArrayList<Object[]> result = new ArrayList<Object[]>();
if (con != null) {
try {
int i = 0;
if (param == null) {
stat = con.createStatement();
rs = stat.executeQuery(str_SQL);
} else {
pst = con.prepareStatement(str_SQL);
for (i = 0; i < param.length; i++) {
pst.setObject(i + 1, param[i]);
}
rs = pst.executeQuery();
pst.clearParameters();
}

ArrayList<Object> arr;
ResultSetMetaData rsMetaData = rs.getMetaData();
int numberOfColumns = rsMetaData.getColumnCount();
while (rs.next()) {
arr = new ArrayList<Object>();
for (i = 1; i < numberOfColumns + 1; i++) {
arr.add(rs.getObject(i));
}
result.add(arr.toArray());
}
} catch (SQLException e) {
System.err.println("executeQuery Exception :" + e.toString());
throw e;
} finally {
CloseConn(isCloseConn);
}
} else {
System.err.println("Connection is null");
throw new NullPointerException("Connection is null");
}
return result;
}

/**
* 查詢資料(請記得要關閉Result Set)
*
* @param str_SQL
* 查詢語法
* @return 回傳查詢得到的result set
*/
public ResultSet executeQueryResultset(String str_SQL) throws SQLException,NullPointerException {
return executeQueryResultset(str_SQL,null);
}
/**
* 查詢資料(請記得要關閉Connection)
*
* @param str_SQL
* 查詢語法
* @param param
* 參數設定,SQL語法中以?指定,順序即為?的順序,如 where name=? (此參數可為null)
* @return 回傳查詢得到的result set
*/
public ResultSet executeQueryResultset(String str_SQL, Object[] param) throws SQLException,NullPointerException {
if(isIsdebug()){
System.out.println("executeQuery:"+str_SQL);
if(param!=null)
for(int i=0;i<param.length;i++)
System.out.println("Parameter "+ i +":"+param[i]);
}

ResultSet result = null;
if (con != null) {
try {
int i = 0;
if (param == null) {
stat = con.createStatement();
rs = stat.executeQuery(str_SQL);
} else {
pst = con.prepareStatement(str_SQL);
for (i = 0; i < param.length; i++) {
pst.setObject(i + 1, param[i]);
}
rs = pst.executeQuery();
pst.clearParameters();
}

result=rs;
} catch (SQLException e) {
System.err.println("executeQueryResultset Exception :" + e.toString());
throw e;
}
} else {
System.err.println("Connection is null");
throw new NullPointerException("Connection is null");
}
return result;
}

/**
* 查詢資料,並傳回查詢所傳回的結果集第一個資料列的第一個資料行
*
* @param str_SQL
* 查詢語法
* @return 每列一筆資料,每筆資料的欄位存於 Object Array中
*/
public Object executescalar(String str_SQL) throws SQLException,NullPointerException {
return executescalar(str_SQL, null);
}
/**
* 查詢資料,並傳回查詢所傳回的結果集第一個資料列的第一個資料行
*
* @param str_SQL
* 查詢語法
* @param param
* 參數設定,SQL語法中以?指定,順序即為?的順序,如 where name=? (此參數可為null)
* @return 每列一筆資料,每筆資料的欄位存於 Object Array中
*/
public Object executescalar(String str_SQL, Object[] param) throws SQLException,NullPointerException {
return executescalar(str_SQL, param, true);
}

/**
* 查詢資料,並傳回查詢所傳回的結果集第一個資料列的第一個資料行
*
* @param str_SQL
* 查詢語法
* @param param
* 參數設定,SQL語法中以?指定,順序即為?的順序,如 where name=? (此參數可為null)
* @param isCloseConn
* 是否關閉連線
* @return 每列一筆資料,每筆資料的欄位存於 Object Array中
*/
public Object executescalar(String str_SQL, Object[] param,
boolean isCloseConn) throws SQLException,NullPointerException {
if(isIsdebug()){
System.out.println("executeQuery:"+str_SQL);
if(param!=null)
for(int i=0;i<param.length;i++)
System.out.println("Parameter "+ i +":"+param[i]);
System.out.println("isCloseConn :"+isCloseConn);
}
Object result = null;
if (con != null) {
try {
int i = 0;
if (param == null) {
stat = con.createStatement();
rs = stat.executeQuery(str_SQL);
} else {
pst = con.prepareStatement(str_SQL);
for (i = 0; i < param.length; i++) {
pst.setObject(i + 1, param[i]);
}
rs = pst.executeQuery();
pst.clearParameters();
}

ResultSetMetaData rsMetaData = rs.getMetaData();
int numberOfColumns = rsMetaData.getColumnCount();
if (rs.next() && numberOfColumns>=1) {
result=rs.getObject(1);
}
} catch (SQLException e) {
System.err.println("executescalar Exception :" + e.toString());
throw e;
} finally {
CloseConn(isCloseConn);
}
} else {
System.err.println("Connection is null");
throw new NullPointerException("Connection is null");
}
return result;
}


/**
* 執行SQL
*
* @param str_SQL
* 查詢語法
* @return 是否成功
*/
public boolean execute(String str_SQL) throws SQLException,NullPointerException {
return execute(str_SQL, null);
}

/**
* 執行SQL
*
* @param str_SQL
* 查詢語法
* @param param
* 參數設定,SQL語法中以?指定,順序即為?的順序,如 where name=? (此參數可為null)
* @return 是否成功
*/
public boolean execute(String str_SQL, Object[] param) throws SQLException,NullPointerException {
return execute(str_SQL, param, true);
}

/**
* 執行SQL
*
* @param str_SQL
* 查詢語法
* @param param
* 參數設定,SQL語法中以?指定,順序即為?的順序,如 where name=? (此參數可為null)
* @param isCloseConn
* 是否關閉連線
* @return 是否成功
*/
public boolean execute(String str_SQL, Object[] param, boolean isCloseConn) throws SQLException,NullPointerException {
if(isIsdebug()){
System.out.println("executeQuery:"+str_SQL);
if(param!=null)
for(int i=0;i<param.length;i++)
System.out.println("Parameter "+ i +":"+param[i]);
System.out.println("isCloseConn :"+isCloseConn);
}
boolean result = false;
if (con != null) {
try {
int i = 0;
if (param == null) {
stat = con.createStatement();
result = stat.execute(str_SQL);
} else {
pst = con.prepareStatement(str_SQL);
for (i = 0; i < param.length; i++) {
pst.setObject(i + 1, param[i]);
}
result = pst.execute();
pst.clearParameters();
}
} catch (SQLException e) {
System.err.println("execute Exception :" + e.toString());
throw e;
} finally {
CloseConn(isCloseConn);
}
} else {
System.err.println("Connection is null");
throw new NullPointerException("Connection is null");
}
return result;
}
/**
* 關閉連線
*/
public void CloseConn() {
CloseConn(true);
}

/**
* 關閉連線
* @param isCloseConn 是否關閉Connection
*/
public void CloseConn(boolean isCloseConn) {
if(isIsdebug()){
System.out.println("CloseConn:"+isCloseConn);
}
try {
if (rs != null) {
rs.close();
rs = null;
}
if (stat != null) {
stat.close();
stat = null;
}
if (pst != null) {
pst.close();
pst = null;
}
} catch (SQLException e) {
//System.err.println("Close Exception :" + e.toString());
} finally {
try {
if (con != null && isCloseConn) {
con.close();
isClosed=true;
}
} catch (SQLException e) {
//System.err.println("Close Exception :" + e.toString());
}
}
}


int allpage=0;

public int getAllpage() {
return allpage;
}
public void setAllpage(int allpage) {
this.allpage = allpage;
}
/**
* 查詢分頁資料
* @param colunms
* @param from
* @param where
* @param group
* @param order
* @param fpage
* @param pages
* @return
* @throws SQLException
* @throws NullPointerException
*/
public ArrayList<Object[]> selectPage(String colunms,String from,String where,String group,String having,String order,int fpage,int pages) throws SQLException,NullPointerException{
return selectPage(colunms,from,where,group,having,order,fpage,pages,null);
}
/**
* 查詢分頁資料
* @param colunms
* @param from
* @param where
* @param group
* @param order
* @param fpage
* @param pages
* @param param
* @return
* @throws SQLException
* @throws NullPointerException
*/
public ArrayList<Object[]> selectPage(String colunms,String from,String where,String group,String having,String order,int fpage,int pages, Object[] param) throws SQLException,NullPointerException{
return selectPage(colunms,from,where,group,having,order,fpage,pages,param,true);
}
/**
* 查詢分頁資料
* @param colunms
* @param from
* @param where
* @param group
* @param order
* @param fpage
* @param pages
* @param param
* @param isCloseConn
* @return
* @throws SQLException
* @throws NullPointerException
*/
public ArrayList<Object[]> selectPage(String colunms,String from,String where,String group,String having,String order,int fpage,int pages, Object[] param, boolean isCloseConn) throws SQLException,NullPointerException{
return selectPage(colunms, from, where, group, having, order, fpage, pages, true, 0, param, isCloseConn);
}
/**
* 查詢分頁資料
* @param colunms
* @param from
* @param where
* @param group
* @param having
* @param order
* @param fpage
* @param pages
* @param isCountPage
* @param allpageCount
* @param param
* @param isCloseConn
* @return
* @throws SQLException
* @throws NullPointerException
*/
public ArrayList<Object[]> selectPage(String colunms,String from,String where,String group,String having,String order,int fpage,int pages,boolean isCountPage,int allpageCount, Object[] param, boolean isCloseConn) throws SQLException,NullPointerException{
ArrayList<Object[]> result=null;
try{
String sql;
if(!isCountPage){
allpage=allpageCount;
}else{
int intRowCount;
sql="select count(*) from (select ";
if(colunms!=null){
sql+="" + colunms + " \n ";
}else{
sql+="* \n ";
}
sql+="from " + from.toString() + " \n ";
if(where!=null){
sql+="where " + where + " \n ";
}
if(group!=null){
sql+="group by " + group + " \n ";
}
if(having!=null){
sql+="having " + group + " \n ";
}
sql+=") selectcount \n ";

Object temp=executescalar(sql,param,false);
if(temp==null){
return result;
}
intRowCount=Integer.parseInt(temp.toString());
allpage=(intRowCount+fpage-1) / fpage;
}
int spage;

if(pages > allpage ){// pages == 0){
pages = 1;
}
spage=(pages-1)*fpage;
//sql="select " + colunms + " \n from " + from + " \n where " + where + " \n order by " + order + " \n limit "+ spage +","+fpage ;
sql="select ";
if(colunms!=null){
sql+= colunms + " \n ";
}else{
sql+="* \n ";
}
sql+="from " + from + " \n ";
if(where!=null){
sql+="where " + where + " \n ";
}
if(group!=null){
sql+="group by " + group + " \n ";
}
if(having!=null){
sql+="having " + group + " \n ";
}
if(order!=null){
sql+="order by " + order + " \n ";
}
sql+="limit "+ spage +","+fpage;
result=executeQuery(sql, param, false);
} catch (NullPointerException e) {
System.err.println("NullPointerException Exception :" + e.toString());
throw e;
} catch (SQLException e) {
System.err.println("SQLException Exception :" + e.toString());
throw e;
} finally {
CloseConn(isCloseConn);
}
return result;
}

}

使用範例程式

package common.test;

import static org.junit.Assert.*;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import common.DBBaseIO;

public class DBBaseIOTest {

DBBaseIO dbio;
int m_id;

String m_name;
@Before
public void setUp() throws Exception {
dbio=new DBBaseIO(0);
m_id=20100111;
m_name="tester" + m_id;
}

@After
public void tearDown() throws Exception {
dbio.CloseConn();
dbio=null;
}

@Test
public void testExecuteString() {
String msg="\n# testExecuteString\n Drop and Create :\t TestTable" + m_id;
System.out.println(msg);//DROP TABLE IF EXISTS TestTable" + m_id + ";
String str_Sql = "CREATE TABLE TestTable" + m_id + " ( " +
"`test_id` int(10) unsigned NOT NULL auto_increment, " +
"`member_id` int(10) unsigned NOT NULL, "+
"`member_name` varchar(45) NOT NULL, " +
"PRIMARY KEY (`test_id`) "+
") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; ";
try{
dbio.execute(str_Sql);

}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}
}

@Test
public void testExecuteUpdateString() {
String msg="\n# testExecuteUpdateString\n Insert :\tID=" + m_id + "\tName=" + m_name;
System.out.println(msg);
String str_Sql="Insert into TestTable" + m_id + " (member_id,member_name) values('" + m_id + "','" + m_name + "')";
try{
int result=dbio.executeUpdate(str_Sql);
if(result<=0){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + result);
}
}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}
}

@Test
public void testExecuteQueryString() {
String msg="\n# testExecuteQueryString\n Select ALL :";
System.out.println(msg);
String str_Sql="Select * from TestTable" + m_id + "";
try{
ArrayList<Object[]> result=dbio.executeQuery(str_Sql);
if(result.size()<=0){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + result);
}else{
for(int i=0;i<result.size();i++){
for(int j=0;j<result.get(i).length;j++){
System.out.print(result.get(i)[j] + "\t");
}
System.out.println();
}
}
}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}
}

@Test
public void testexecuteQueryResultset() {
String msg="\n# testexecuteQueryResultset\n Select ALL :";
System.out.println(msg);
String str_Sql="Select * from TestTable" + m_id + "";
try{

ResultSet rs=dbio.executeQueryResultset(str_Sql);
ResultSetMetaData rsMetaData = rs.getMetaData();
int numberOfColumns = rsMetaData.getColumnCount();
while (rs.next()) {
for (int i = 1; i < numberOfColumns + 1; i++) {
System.out.print(rs.getObject(i) + "\t");
}
System.out.println();
}

dbio.CloseConn();
}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}
}

@Test
public void testselectPage() {
String msg="\n# testselectPage\n Insert 98 User";
System.out.println(msg);
String str_Sql="";
try{

for(int i=1;i<=98;i++){
str_Sql="Insert into TestTable" + m_id + " (member_id,member_name) values('" + m_id + i + "','" + m_name + "_" + i + "')";
int result=dbio.executeUpdate(str_Sql,null,false);
if(result<=0){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + result);
}
}
str_Sql="";
ArrayList<Object[]> result=dbio.selectPage("member_id,member_name", "TestTable" + m_id, null, "member_id", 10, 10, null, true);
if(result.size()<=0){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + result);
}else{
for(int i=0;i<result.size();i++){
for(int j=0;j<result.get(i).length;j++){
System.out.print(result.get(i)[j] + "\t");
}
System.out.println();
}
}
}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}


}


@Test
public void testExecuteUpdateStringObjectArray() {
m_name+="_Updateed";
String msg="\n# testExecuteUpdateStringObjectArray\n Update :\tID=" + m_id + "\tName=" + m_name;
System.out.println(msg);
String str_Sql="Update TestTable" + m_id + " Set member_name=? WHERE member_id=?";
Object[] param={m_name,m_id};
try{
int result=dbio.executeUpdate(str_Sql,param);
if(result<=0){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + result);
}
}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}
}

@Test
public void testExecuteQueryStringObjectArray() {
String msg="\n# testExecuteQueryStringObjectArray\n Select :\tID=" + m_id;
System.out.println(msg);
String str_Sql="Select * from TestTable" + m_id + " where member_id=?";
Object[] param={m_id};
try{
ArrayList<Object[]> result=dbio.executeQuery(str_Sql,param);
if(result.size()<=0){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + result);
}else{
for(int i=0;i<result.size();i++){
for(int j=0;j<result.get(i).length;j++){
System.out.print(result.get(i)[j] + "\t");
}
System.out.println();
}
}
}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}
}

@Test
public void testExecuteUpdateStringObjectArrayBoolean() {
String msg="\n# testExecuteUpdateStringObjectArrayBoolean\n Delete :\tID=" + m_id + "\tName=" + m_name;
System.out.println(msg);
String str_Sql="Delete from TestTable" + m_id + " WHERE member_id=?";
Object[] param={m_id};
try{
int result=dbio.executeUpdate(str_Sql,param,false);
if(result<=0){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + result);
}
}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}finally{
dbio.CloseConn();
}
}

@Test
public void testExecuteQueryStringObjectArrayBoolean() {
String msg="\n# testExecuteQueryStringObjectArrayBoolean\n Select :\tID=" + m_id;
System.out.println(msg);
String str_Sql="Select * from TestTable" + m_id + " where member_id=?";
Object[] param={m_id};
try{
ArrayList<Object[]> result=dbio.executeQuery(str_Sql,param,false);
if(result.size()>0){
for(int i=0;i<result.size();i++){
for(int j=0;j<result.get(i).length;j++){
System.out.print(result.get(i)[j] + "\t");
}
System.out.println();
}
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + result);
}
}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}finally{
dbio.CloseConn();
}
}





@Test
public void testExecuteStringObjectArrayBoolean() {
String msg="\n# testExecuteStringObjectArrayBoolean\n Drop :\t TestTable_" + m_id;
System.out.println(msg);
String str_Sql="DROP TABLE IF EXISTS TestTable" + m_id + ";" ;

try{
dbio.execute(str_Sql,null,false);

}catch(Exception e){
fail(msg + "\n\n" + str_Sql + "\n\nResult:" + e.getMessage());
}finally{
dbio.CloseConn();
}
}



}

總結

我在開發時採用ArrayList把資料給回傳回來,因此相反的就沒有辦法回傳欄位名稱(不過我想你在寫SQL時應該已經可以指定自己要用的欄位名稱了吧)。

相關文章

[JAAS] JBoss下動態網站使用資料庫設定認證授權

序言

雖說我以前一直很不想再Try Java Tool弄很久~因為開發速度還是微軟順手些,但看來我還是逃脫不了。
總之~這篇是最近我實作時的紀錄,主要是要用Java預設的安全機制,完成網站的認證授權(也就是登入登出、 Login and Logout)。
通常書到用時方恨少,這句話這時就應驗了,腦中有的資訊太少,所以這個東西花了我不少功夫。
如果有任何不標準的用法,請見諒,很多都是我從網路找來的資訊組合而成的,我很懶得去查官方的英文文件(官方文件有很多其實也沒寫的很詳細...)。
歡迎有更標準的用法的話,請留下文章、連結等資料指正我。

開發環境

JBoss設定

  1. 資料庫連接器:將資料庫連接器【mysql-connector-java-5.1.10-bin.jar】放在Jboss的伺服器函式庫資料夾,如【jboss\server\default\lib】
  2. 資料庫連線設定:連線設定檔是用xml定義的,可直接複製JBoss提供的範本(位於【jboss\docs\examples\jca\mysql-ds.xml】)到【jboss\server\default\deploy】,並將資料庫設為你的MySQL連線設定。(如果用其他資料庫的話,在範本資料夾裡大都有範例)
    這邊比較需要注意的是等下要用到的名稱
    • jndi-name:等下設定驗證方式時會用到的名稱,例如下面範例的【MySqlDS】
    <?xml version="1.0" encoding="UTF-8"?>

    <!-- See http://www.jboss.org/community/wiki/Multiple1PC for information about local-tx-datasource -->
    <!-- $Id: mysql-ds.xml 88948 2009-05-15 14:09:08Z jesper.pedersen $ -->
    <!-- Datasource config for MySQL using 3.0.9 available from:
    http://www.mysql.com/downloads/api-jdbc-stable.html
    -->

    <datasources>
    <local-tx-datasource>
    <jndi-name>MySqlDS</jndi-name>
    <connection-url>jdbc:mysql://mysql-hostname:3306/jbossdb</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>x</user-name>
    <password>y</password>
    <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
    <!-- should only be used on drivers after 3.22.1 with "ping" support
    <valid-connection-checker-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLValidConnectionChecker</valid-connection-checker-class-name>
    -->
    <!-- sql to call when connection is created
    <new-connection-sql>some arbitrary sql</new-connection-sql>
    -->
    <!-- sql to call on an existing pooled connection when it is obtained from pool - MySQLValidConnectionChecker is preferred for newer drivers
    <check-valid-connection-sql>some arbitrary sql</check-valid-connection-sql>
    -->

    <!-- corresponding type-mapping in the standardjbosscmp-jdbc.xml (optional) -->
    <metadata>
    <type-mapping>mySQL</type-mapping>
    </metadata>
    </local-tx-datasource>
    </datasources>


    另外,如果覺得連線密碼這樣寫明碼太危險,可參考JBoss community的文件加密儲存
  3. 驗證方式設定:在JBoss的設定目錄下【jboss\server\default\conf】,有個【login-config.xml】,這個就是設定帳號驗證的方式(如果沒有這個檔也可以自己新增)
    裡面的參數
    • UserAA: 我設定的驗證來源名稱,這個在專案中會做對應的設定。
    • dsJndiName: 這邊就會對應到上一個設定的資料庫來源名稱MySqlDS
    • principalsQuery:這邊要寫查詢帳號密碼的SQL
    • rolesQuery:這邊要寫使用者角色的查詢SQL,角色會關係到後面網站權限的設定,第一個資料【roleName】所查詢出來的值我們這邊假設可能會有admin與guest,第二個資料是群組,我目前也不清楚是做什麼的,先依據官方範例給固定文字
    <?xml version='1.0'?>
    <policy>
    <application-policy name="UserAA">
    <authentication>
    <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag = "required">
    <module-option name = "dsJndiName">java:/MySqlDS</module-option>
    <module-option name = "principalsQuery">SELECT passwd FROM userdb.usersTab where userID=?</module-option>
    <module-option name = "rolesQuery">SELECT roleName,'Roles' FROM userdb.userRolesTab where userID=?</module-option>
    </login-module>
    </authentication>
    </application-policy>
    </policy>

MySQL

以下使用SQL script建立所需的資料庫與資料表,我將同時產生兩個預設的帳號分別屬於不同的權限角色,以下比較需要注意的部份是
  • 主要用到的資料來源是UsersTab資料表中的兩個欄位userID、passwd,還有UserRolesTab的兩個欄位userID、roleName,這部份不知道是不是有型態限制或是長度限制的問題,我用別的規格下去可能就無法認證。
  • 往下的Trigger是可有可無的,是我建立還方便管理資料庫用的
  • 最下面的insert是預設帳號與角色資料的建立
    • boss/0000:角色為admin
    • test/0000:角色為guest
CREATE DATABASE  IF NOT EXISTS `userdb` /*!40100 DEFAULT CHARACTER SET utf8 */;


DROP TABLE IF EXISTS `userdb`.`UsersTab`;
CREATE TABLE `userdb`.`UsersTab` (
`userID` varchar(64) NOT NULL,
`passwd` varchar(64) NOT NULL,
`createtime` datetime DEFAULT NULL,
`updatetime` datetime DEFAULT NULL,
PRIMARY KEY (`userID`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `userdb`.`UserRolesTab`;
CREATE TABLE `userdb`.`UserRolesTab` (
`userID` varchar(64) NOT NULL,
`roleName` varchar(32) NOT NULL DEFAULT 'guest',
`createtime` datetime DEFAULT NULL,
PRIMARY KEY (`userID`,`roleName`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;


DELIMITER $$
drop TRIGGER IF EXISTS `userdb`.UsersTab_Insert$$
CREATE TRIGGER `userdb`.UsersTab_Insert BEFORE INSERT ON `userdb`.UsersTab FOR EACH ROW
BEGIN
if new.createtime is null then
Set new.createtime = now();
end if;
if new.updatetime is null then
Set new.updatetime = now();
end if;
END$$

drop TRIGGER IF EXISTS `userdb`.UsersTab_Update$$
CREATE TRIGGER `userdb`.UsersTab_Update BEFORE UPDATE ON `userdb`.UsersTab FOR EACH ROW
BEGIN
if new.updatetime is null or old.updatetime=new.updatetime then
Set new.updatetime = now();
end if;
END$$

drop TRIGGER IF EXISTS `userdb`.UsersTab_Delete$$
CREATE TRIGGER `userdb`.UsersTab_Delete AFTER DELETE ON `userdb`.UsersTab FOR EACH ROW
BEGIN
delete from `userdb`.UserRolesTab where userID=old.userID;
END$$

drop TRIGGER IF EXISTS `userdb`.UserRolesTab_Insert$$
CREATE TRIGGER `userdb`.UserRolesTab_Insert BEFORE INSERT ON `userdb`.UserRolesTab FOR EACH ROW
BEGIN
if new.createtime is null then
Set new.createtime = now();
end if;
END$$
DELIMITER ;

insert into userdb.UsersTab (userID,passwd) values('boss','0000');
insert into userdb.UserRolesTab (userID,roleName) values('boss','admin');

insert into userdb.UsersTab (userID,passwd) values('test','0000');
insert into userdb.UserRolesTab (userID,roleName) values('test','guest');

Dynmic Web Project

動態網站的設定主要是在【WEB-INF】資料夾中新增一個【jboss-web.xml】,內容如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.3V2//EN" "http://www.jboss.org/j2ee/dtd/jboss-web_3_2.dtd">

<jboss-web>
<security-domain>java:/jaas/UserAA</security-domain>
</jboss-web>
然後修改【web.xml】。這邊我採用Form認證的方式,也就是用網頁提供Login的介面。
  • security-constraint:這一段是設定哪些路徑可由哪些Role來存取,也就是權限設定。這部份有用到的角色一定要在下面的【security-role】有定義到。另外如果想設定成只有有登入的人才能存取,可將【role-name】設定成【*】。
  • login-config:這一段就是定義Login的方式與參照頁面(另依種方式設定為BASIC)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
...
<security-constraint>
<web-resource-collection>
<web-resource-name></web-resource-name>
<url-pattern>/test/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name></web-resource-name>
<url-pattern>/Login</url-pattern>
<url-pattern>/Logout</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name></realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login.jsp?msg=Incorrect</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>guest</role-name>
</security-role>
...
</web-app>

JSP & Servlet

再來我針對登入部份的網頁進行開發,主要會建立JSP:login.jsp與Servlet:Login、Logout。
實際上真正的登入網頁會是login.jsp,但因為如果使用者直接輸入login.jsp的網址後,進行登入成功後系統會不知該導到哪一頁(似乎也沒辦法設定預設導頁,網路上是有另一種作法如果有需要可自行參考)。
我這邊是自己想了個比較簡單的邏輯性解法,就是做一個Login的Servlet裡面直接會跳到首頁作為預設網頁,
然後將這個Login servlet設定必須登入才能存取(也就是上一步的web.xml中設定的),讓登入的連結以這個Servlet為主就會自動導自login.jsp。
login.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Login</title>
</head>
<body>

<h2 align="center">Login</h2>
<table border="0" align="center">
<tr>
<td>
<form method="POST" action="j_security_check">
<div>User ID :
<input type="text"
name="j_username">
</div>
<div>Password :
<input type="password"
name="j_password">
</div>
<div><input type="submit" value="Log In"></div>

<div style="color: red;"><%=(request.getParameter("msg")!=null)?request.getParameter("msg"):"" %></div>

</form>
</td>
</tr>
</table>

</body>
</html>
Login servlet
package view;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* Servlet implementation class Login
*/
public class Login extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* @see HttpServlet#HttpServlet()
*/
public Login() {
super();
// TODO Auto-generated constructor stub
}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.sendRedirect(request.getContextPath() + "/index.jsp");
}
}

Logout servlet
package view;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class Logout extends HttpServlet {
private static final long serialVersionUID = 1L;

/**
* @see HttpServlet#HttpServlet()
*/
public Logout() {
super();
}

protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().invalidate();
response.sendRedirect(request.getContextPath() + "/");
}
}

總結

以上設定完成後,就可以進到你的應用程式,然後存取Login servlet看看能不能正常登入登出了。
在這邊補充一些資訊,如果要在程式中取得登入者後的帳號,可以用【request.getUserPrincipal()】來取得Principal物件,如果不是null就可以呼叫該物件的getName()方法。
不過我個人比較喜歡用另外一個物件【org.jboss.security.SecurityAssociation】中的【getPrincipal()】也可以。
如果是要判斷使用者的角色,可使用【request.isUserInRole("admin")】方法來知道該使用者是否屬於admin這個角色。

參考資料

[分享]PHP版網頁原始碼處理函式

序言

這是我依VB.Net的網頁原始碼處理函式翻過來的PHP版程式,可以取得網頁後,再針對特定標簽進行切割處理。

函式原始碼

我將以下原始碼存在【myClass】目錄的【HTMLParser.php】檔
<?php
Class HTMLParser{
function getHTML($url,$method="GET",$param=NULL,$noCashe=False){
//$param = array("name" => 'tim',"content" => 'test');

if($noCashe){
$cashe="Cache-Control: no-cache\r\n";
}else{
$cashe="";
}
if($method=="GET" and $param!=NULL){
$data="";
foreach ($param as $k => $v) {
if($data!="") $data.="&";
$data.= "$k=".htmlentities($v);
}
$url.="?".$data;
}

if($method=="POST" and $param!=NULL){
$data = http_build_query($param);
$contentLength="Content-length:".strlen($data)."\r\n";
$opts = array(
'http'=>array(
'method'=>$method,
'header'=>"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\r\n"
. "Accept-language: zh-tw\r\n"
. "Content-Typ: application/x-www-form-urlencoded\r\n"
. $cashe . $contentLength,
'content' => $data
)
);
}else{
$opts = array(
'http'=>array(
'method'=>$method,
'header'=>"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\r\n"
. "Accept-language: zh-tw\r\n"
. "Content-Typ: application/x-www-form-urlencoded\r\n"
. $cashe
)
);

}
$context = stream_context_create($opts);
// Open the file using the HTTP headers set above
return file_get_contents($url, false, $context);
//return file_get_contents($url);
}
//取得網頁的Body區段
function getHTMLBody($url,$postValue=NULL,$noCashe= False){
$html=NULL;
$htmlBody=NULL;
if($postValue!=NULL){
$html = $this->getHTML($url,"POST",$postValue,$noCashe);
}else{
$html = $this->getHTML($url,"GET", $postValue,$noCashe);
}
If(html!=NULL) $this->getHTMLTagContain($html, "body", $htmlBody);

return $htmlBody;
}
//取得HTML中第一個符合的標籤內容的指標,並將取得的標籤內容寫入參數中
function getHTMLTagContain($html,$tag,&$contain=NULL,&$indexEnd= -1){
$tag = strtolower($tag);
$contain = NULL;
$indexBegin= -1;
$indexEnd = -1;
$indexbBegin= -1;
If($html !=NULL){
$indexBegin = strpos(strtolower($html),"<" . $tag);
if (gettype($indexBegin)!="integer") $indexBegin=-1;
If($indexBegin > -1){
$indexbBegin = strpos($html,">",$indexBegin);
if (gettype($indexbBegin)!="integer"){
$indexbBegin=-1;
}else{
$indexbBegin+=1;
}
$findTag=False;
$indexEnd=$indexbBegin;
$lastStart=$indexbBegin;
$stopLimit2 = 9999;
do{
$indexEnd=strpos(strtolower($html),"</" . $tag,$indexEnd);
if(gettype($indexEnd)!="integer") $indexEnd=-1;
If ($indexEnd > -1){
if(gettype(strpos(substr($html,$lastStart, $indexEnd - $lastStart),"<" . $tag))=="integer"){
$lastStart=$indexEnd;
$indexEnd=$indexEnd+strlen("</" . $tag);
$findTag=True;
}else{
$findTag=False;
}
}else{
$findTag=False;
}
$stopLimit2--;
}while($findTag AND $stopLimit2>0);
If ($indexEnd > -1){
$contain = substr($html,$indexbBegin, $indexEnd - $indexbBegin);

$indexEnd= strpos($html,">",$indexEnd);
if(gettype($indexEnd)!="integer"){
$indexEnd=-1;
}else{
$indexEnd+=1;
}
}
}
}
//echo $contain;
Return $indexbBegin;
}
//取得HTML中第一個符合的標籤屬性的指標,並將取得的標籤內容與該屬性內容寫入參數中
function getHTMLTagAtt($html,$tag,$attName,&$att=NULL,&$contain=NULL,&$indexEnd= -1){
$tag = strtolower($tag);
$attName = strtolower($attName);
$contain = NULL;
$att = NULL;
$indexEnd = -1;
$indexBegin= -1;
$indexaBegin= -1;
$indexbEnd = -1;
If($html !=NULL){
$indexBegin = 0;
$stopLimit1 = 9999;
do{
$indexbEnd = -1;
$indexBegin = strpos(strtolower($html),"<" . $tag,$indexBegin);
if (gettype($indexBegin)!="integer") $indexBegin=-1;
If($indexBegin > -1){
$indexbEnd= strpos($html,">",$indexBegin);
if (gettype($indexbEnd)!="integer") $indexbEnd=-1;
If($indexbEnd > -1){
$indexaBegin = strpos(strtolower(str_replace("\"", "'",substr($html,0, $indexbEnd))),$attName . "='",$indexBegin);
if(gettype($indexaBegin)!="integer") $indexaBegin=-1;
If($indexaBegin > -1){
$sign = substr($html,$indexaBegin + strlen($attName . "="), 1);
$indexaBegin+=strlen($attName . "='");
$indexaEnd= strpos(substr($html,0, $indexbEnd),$sign,$indexaBegin);
if(gettype($indexaEnd)!="integer") $indexaEnd=-1;
If($indexaEnd > -1){
$att = substr($html,$indexaBegin, $indexaEnd - $indexaBegin);
}
}else{
$indexBegin = $indexbEnd + 1;
continue;
}
$indexbEnd+=1;
}

$findTag=False;
$indexEnd=$indexbEnd;
$lastStart=$indexbEnd;
$stopLimit2 = 9999;
do{
$indexEnd=strpos(strtolower($html),"</" . $tag,$indexEnd);
if(gettype($indexEnd)!="integer") $indexEnd=-1;
If ($indexEnd > -1){
if(gettype(strpos(substr($html,$lastStart, $indexEnd - $lastStart),"<" . $tag))=="integer"){
$lastStart=$indexEnd;
$indexEnd=$indexEnd+strlen("</" . $tag);
$findTag=True;
}else{
$findTag=False;
}
}else{
$findTag=False;
}
$stopLimit2--;
}while($findTag==True AND $stopLimit2>0);
If ($indexEnd > -1){
$contain = substr($html,$indexbEnd, $indexEnd - $indexbEnd);
$indexEnd= strpos($html,">",$indexEnd);
if(gettype($indexEnd)!="integer"){
$indexEnd=-1;
}else{
$indexEnd+=1;
}
}
}else{
break;
}
$stopLimit1--;
}while($att == NULL AND $stopLimit1>0);
}
Return $indexbEnd;
}
}
?>

使用範例

<?php
    include 'myClass/HTMLParser.php';
    $htmlParser=new HTMLParser();
    $url = "http://allen080.blogspot.com/2009/05/vbnet20.html";
    //取得整個網頁的HTML
    $htmlAll = $htmlParser->getHTML($url);
    //取得網頁Body的部份
    $htmlBody = $htmlParser->getHTMLBody($url);
    If ($htmlBody!=NULL){
        $indexEnd = strpos($htmlBody,"<span class='item-control blog-admin'>");
        if(gettype($indexEnd)!="integer")    $indexEnd=-1;
        $valueAttribute = NULL;
        $valueContain = NULL;
        If($indexEnd > 0){
            $htmlBody = substr($htmlBody,$indexEnd);
            //取得a標籤1的內容
            $htmlParser->getHTMLTagContain($htmlBody, "span", $valueContain, $indexEnd);
            echo "span標籤的內容: " . $valueContain ."<BR>\n";
            //$htmlBody = substr($htmlBody,$indexEnd);
            //取得a標籤2的內容與href的屬性
            $htmlParser->getHTMLTagAtt($htmlBody, "a", "onclick", $valueAttribute, $valueContain, $indexEnd);
          echo "a標籤的內容與onclick的屬性: " . $valueAttribute ."\t" . ",內容:" . $valueContain;
        }
    }    
?>


執行結果

span標籤的內容:
<a class='quickedit' href='http://www.blogger.com/rearrange?blogID=2698062899592178296&widgetType=HTML&widgetId=HTML1&action=editWidget' onclick='return _WidgetManager._PopupConfig(document.getElementById("HTML1"));' target='configHTML1' title='編輯'>
<img alt='' height='18' src='http://img1.blogblog.com/img/icon18_wrench_allbkg.png' width='18'/>
</a>
<BR>
a標籤的內容與onclick的屬性: return _WidgetManager._PopupConfig(document.getElementById("HTML1"));    ,內容:
<img alt='' height='18' src='http://img1.blogblog.com/img/icon18_wrench_allbkg.png' width='18'/>


總結

這是我為了練習php時寫出的東西~
我個人是覺得滿好用的~
不過有部份邏輯滿複雜的~所以我也不敢保證完全正確或是適用於各種網頁~
如果有人用有發現更好的改進方式請跟我說。


[PHP]字串處理函式

序言

字串處理是常用的功能,大多數語言對這部份也有許多大同小異的函式,以下列出比較常用或我覺得特別需注意的函式。

尋找字串

strpos($string,$find[,$start]):尋找第一個在字串中出現的字詞,傳回0開始的數字位置。【$string】是被找的字串,【$find】是尋找的目標字詞,【$start】是代表尋找的起始位置。→官方文
這個函式有一個極需注意的部份,就在於它的回傳值,找到的話會回傳位置,也就是個0開始的數字;但沒找到的話就機車了,以VB為例的IndexOf函式是類似這個函式的功能,如果沒找到會回傳【-1】,但在PHP不同版本中,會回傳的值居然是False,那就算了,聽說這是4.0b3版以後的PHP是這樣,更早之前的PHP甚至聽說回傳的連False都不是。於是就有人想出了判斷型態的方法,具體實現方式如下

<?php
    //尋找第一個在字串中出現的字詞
    function str_indexof($string,$find,$start=0){
        $index=strpos($string,$find,$start);
        if(gettype($index)!="integer")    $index=-1;
        return $index;
    }
   
    $string='abcdefghijk';
    $find='l';
    echo str_indexof($string,$find) . "\n";    //-1
    $find='a';
    echo str_indexof($string,$find,3) . "\n";    //-1
    $find='i';
    echo str_indexof($string,$find,3) . "\n";    //8
?>


子字串

substr($string[[,$start],$length]):取得子字串,傳回子字串內容。【$string】是來源字串,【$start】是代表尋找的起始位置,【$length】是子字串長度。→官方文
這個函式需注意的部份在於它對中文字(UTF-8)會以每個字3字元的方式計算,所以切割時如果是中文字就要以3的倍數遞增。有另外的函式可對含中文的函式做計算找子字串,但如果是用上面談到的strpos函式來找指標,那就可以不用擔心這個問題。具體實現方式如下

<?php
    $string = '這是substr的測試';
    echo substr($string,0,13) . "\n";    //這是substr�
    $string = '這是mb_substr的測試';
    echo mb_substr($string,0,13,'UTF-8') . "\n";    //這是mb_substr的測
?>


字串長度

strlen($string):傳回字串長度。【$string】是來源字串。→官方文件
這個函式與上面一樣對中文字(UTF-8)會以每個字3字元的方式計算。

轉換大小寫

strtoupper($string):回傳轉換為英文大寫字串。strtolower($string):取得轉換為英文小寫字串。【$string】是來源字串。→官方文件
這個函式與上面一樣對中文字(UTF-8)會以每個字3字元的方式計算。

文字切割

split($pattern,$string[,$limit]):將字串依分割條件切割為陣列,回傳結果陣列。【$pattern】是分割條件,可為字串或是正規表達式。【$string】是來源字串。【$limit】最多切出幾個元素。
這個函式較直得注意的有兩點,一個是參數順序,居然不是來源字串...;第二個是分割條件【$pattern】可用正規表達式,有關於正規表達式可參考相關連結。→官方文件

文字驗證

ereg($pattern,$string[,&$arrResult]):依正規表達式驗證字串格式(比對字串時有大小寫之分),回傳True符合或False不符合。→官方文件
eregi($pattern,$string[,&$arrResult]):功能同上,但比對字串時沒有大小寫之分。
【$pattern】是驗證條件正規表達式。【$string】是來源字串。【$arrResult】符合的文字。
這個函式較直得注意的一樣有兩點,一個是參數順序;第二個是驗證條件【$pattern】為正規表達式,如【^[a-z0-9-]{3,8}$】如果要完整比對字串的話,要記得開頭用與結尾分別用【^】與【$】把條件框住,【[a-z0-9-]】代表小寫a~z與0~9及減號都是可用的字元,【{3,8}】代表上數字元可重複的次數為3~8次,可參考相關連結
相關連結
  • PHP官方開發手冊:英文開發手冊。
  • W3School:簡體中文的網站開發程式語言文件,雖然有些不夠完整,不過很適合當工具書或初學參考(除了PHP,其他還有許多網頁語言)。
這裡是關於技術的手札~

也歡迎大家到

倫與貓的足跡



到噗浪來

關心一下我唷!
by 倫
 
Copyright 2009 倫倫3號Beta-Log All rights reserved.
Blogger Templates created by Deluxe Templates
Wordpress Theme by EZwpthemes