The case to use One Hibernate Session Multiple Transactions:
each transaction would NOT affect others.
i.e., open multiple transactions on the same session, even though one transaction rolls back, other transactions can be committed. If one action fails, others should fail too, then we should use one transaction for all actions.
Note:
A rollback with a single Session will lead to that Session being cleared (through "Session.clear()").
So do lazy collections still work if the session is cleared? =>Not of any objects that you loaded up until the rollback. Only for new objects loaded afterwards.
We should load necessary objects to session for each transactional action to avoid LazyInitializationException, even if those objects are loaded before other forward transactional actions, since forward action may be rolled back and clear the session.
BTW, Hibernate Session.merge() is different with Session.update() by:
Item item2 = session.merge(item);
item2 == item; // false, item - DETACHED, item2 - PERSIST
session.update(item); // no return value, make item PERSIST
這周被Quartz折騰了一番。
我們知道,Quartz采用JobDataMap實(shí)現(xiàn)向Job實(shí)例傳送配置屬性,正如Quartz官方文檔說(shuō)的那樣:
How can I provide properties/configuration for a Job instance? The key is the JobDataMap, which is part of the JobDetail object.
The JobDataMap can be used to hold any number of (serializable) objects
which you wish to have made available to the job instance when it
executes.
JobDataMap map = context.getJobDetail().getJobDataMap();
我們通過(guò)map向Job實(shí)例傳送多個(gè)objects,其中有一個(gè)是個(gè)bean,一個(gè)是基本類型。對(duì)于scheduled triggers,我們要求bean對(duì)于所有的序列都不變,包括其屬性,而基本類型可以在Job運(yùn)行過(guò)程中改變,并影響下一個(gè)序列。實(shí)際情況是,對(duì)于下個(gè)序列,bean的屬性被上次的修改了,而基本類型卻維持第一次put到Map里面的值。正好和我們要求的相反。
受bean的影響,以為map里面包含的都是更新的對(duì)象,即每個(gè)序列里面的JobDetail是同一個(gè)對(duì)象,但是基本類型的結(jié)果否認(rèn)了這一點(diǎn)。回頭重新翻閱了下Quartz的文檔:
Now, some additional notes about a job's state data (aka JobDataMap): A
Job instance can be defined as "stateful" or "non-stateful".
Non-stateful jobs only have their JobDataMap stored at the time they
are added to the scheduler. This means that any changes made to the
contents of the job data map during execution of the job will be lost,
and will not seen by the job the next time it executes.
Job有兩個(gè)子接口:StatefulJob and InterruptableJob,我們繼承的是InterruptableJob,或許Quartz應(yīng)該有個(gè)InterruptableStatefulJob。另外StatefulJob不支持并發(fā)執(zhí)行,和我們的需求不匹配,我們有自己的同步控制,Job必須可以并發(fā)運(yùn)行。
然后查看了Quartz的相關(guān)源碼:
// RAMJobStore.storeJob
public void storeJob(SchedulingContext ctxt, JobDetail newJob,
boolean replaceExisting) throws ObjectAlreadyExistsException {
JobWrapper jw = new JobWrapper((JobDetail)newJob.clone()); // clone a new one

.
jobsByFQN.put(jw.key, jw);


}
也就是說(shuō),store里面放的是初始JobDetail的克隆,在序列運(yùn)行完時(shí),只有StatefulJob才會(huì)更新store里面的JobDetail:
// RAMJobStore.triggeredJobComplete
public void triggeredJobComplete(SchedulingContext ctxt, Trigger trigger,
JobDetail jobDetail, int triggerInstCode) {
JobWrapper jw = (JobWrapper) jobsByFQN.get(jobKey);


if (jw != null) {
JobDetail jd = jw.jobDetail;
if (jd.isStateful()) {
JobDataMap newData = jobDetail.getJobDataMap();
if (newData != null) {
newData = (JobDataMap)newData.clone();
newData.clearDirtyFlag();
}
jd.setJobDataMap(newData); // set to new one




}


}
然后,每次序列運(yùn)行時(shí)所用的JobDetail,是存放在Store里面的克隆。
// RAMJobStore.retrieveJob
public JobDetail retrieveJob(SchedulingContext ctxt, String jobName,
String groupName) {
JobWrapper jw = (JobWrapper) jobsByFQN.get(JobWrapper.getJobNameKey(
jobName, groupName));
return (jw != null) ? (JobDetail)jw.jobDetail.clone() : null; // clone a new
}
問(wèn)題很清楚了,存放在Store里面的JobDetail是初始對(duì)象的克隆,然后每個(gè)序列所用的JobDetail, 是Store里面的克隆,只有Stateful job,Store里面的JobDetail才更新。
最有Quartz里面使用的clone():
// Shallow copy the jobDataMap. Note that this means that if a user
// modifies a value object in this map from the cloned Trigger
// they will also be modifying this Trigger.
if (jobDataMap != null) {
copy.jobDataMap = (JobDataMap)jobDataMap.clone();
}
所以對(duì)于前面所講的,修改bean的屬性,會(huì)影響所有clone的對(duì)象,因此,我們可以將基本類型封裝到一個(gè)bean里面,map里面存放的是bean,然后通過(guò)修改bean的屬性,來(lái)達(dá)到影響下一個(gè)序列的目的。
zz: java.sys-con.com
Java servlet technology provides developers with functionality,
scalability, and portability that can't be found in other server-side
languages. One feature of the Java servlet specification that's
commonly used, and sometimes misused, is the HttpSession interface.
This simple interface allows you to maintain a session or state for Web
site visitors.
In my previous article ("Introduction to Session Management," [JDJ,
Vol. 7, issue 9]), I introduced you to session management and the
HttpSession interface. In that article, we walked through using the
HttpSession API to create, use, and destroy session objects for Web
site visitors. The next step is to better understand how to manage the
sessions and those objects in a session. This article will help you
achieve this by helping you understand the following concepts:
- Code-based session management through listeners
- Proper design of the session and the objects it contains
- Controlling what is in the session and why it's there
- Session persistence
- Memory management
The Java APIs discussed in this article are from Sun's Java Servlet 2.3 specification.
Listeners
A listener is an object that's
called when a specified event occurs. There are four listener
interfaces that allow you to monitor changes to sessions and the
objects that are in those sessions:
- HttpSessionListener
- HttpSessionBindingListener
- HttpSessionAttributeListener
- HttpSessionActivationListener
Figure 1 provides a method summary for each of the listener
interfaces. The implementing class that you write will override these
methods to provide the functionality you need.
HttpSessionListener
The HttpSessionListener
interface is used to monitor when sessions are created and destroyed on
the application server. Its best practical use would be to track
session use statistics for a server.
The use of HttpSessionListener requires a configuration entry
in the deployment descriptor, or web.xml file, of the application
server. This entry points the server to a class that will be called
when a session is created or destroyed. The entry required is simple.
All you need is a listener and listener-class element in the following
format. The listener-class element must be a fully qualified class
name.
<listener>
<listener-class>package.Class</listener-class>
</listener>
As you can see in Figure 1, the class that implements this
listener can override two methods: sessionCreated() and
sessionDestroyed(). These methods will be notified when the server
creates or destroys a session.
These methods take an HttpSessionEvent object as a parameter.
HttpSessionEvent is simply a class that represents notifications of
changes to the Web application's sessions. HttpSessionEvent has one
method, getSession(), that returns the HttpSession object that's been
modified.
HttpSessionBindingListener
The
HttpSessionBindingListener interface is implemented when an object
needs to be notified if it's being bound to a session or unbound from a
session.
This interface has two methods, valueBound() and
valueUnbound(), that are notified when the status of the object has
changed (see Figure 1).
These methods have an HttpSessionBindingEvent parameter that
can be used to retrieve the session that the object was bound to and
the name it was given in the session. In Figure 2, you can see the
methods of this object that are used to get the name that's assigned to
the object, the session it's bound to, and the actual object.
HttpSessionAttributeListener
The
HttpSessionAttributeListener interface is used to monitor changes to
attributes in any session on the server. This can be useful when you
know the name assigned to a specific object that gets put into the
session and you want to track how often it's being used.
As with HttpSessionListener, HttpSessionAttributeListener also
requires an entry in the deployment descriptor for the server. This
entry tells the server which class to call when an attribute in a
session has changed.
The HttpSessionAttributeListener interface has three methods -
attributeAdded(), attributeRemoved(), and attributeReplaced(). These
methods, shown in Figure 1, are called by the server when attributes of
a session are changed.
HttpSessionActivationListener
The final listener,
HttpSessionActivationListener, is implemented when an object needs to
know if the session that it's bound to is being activated or passivated
(moved). You would come across this scenario if your session is being
shared across JVMs or your server is persisting the session in a
database or file system.
This interface, displayed in Figure 1, has two methods that
are overridden by the implementing class: sessionDidActivate() and
sessionWillPassivate(). These methods are called when the status of the
session in a JVM is changed.
Session Persistence
Today's J2EE-compliant
servers allow for fault-tolerance and failover to provide support in
the event that a server suddenly becomes unavailable because of
hardware, software, or network failure. This support is usually
provided by allowing two or more application servers, often called a
cluster, to run together and provide backup support for each other. If
one server fails, the others pick up the requests and continue on as if
nothing happened. This allows your Web site visitors to keep going
without interruption.
A proxy server is usually used in front of the application
servers. This server is responsible for directing each HTTP request to
the appropriate server. The proxy server can be set up to ensure that
the server receiving the first request from a user will continue to
receive all subsequent requests from that user. This means that a
session created for the user on the application server will continue to
be available for that user. If the server suddenly fails, there has to
be a system in place to allow the session to continue on without it.
Session persistence allows the session contents to be saved
outside the application server so that other servers can access it.
Figure 3 shows the relationship between the persisted session data and
the application servers that access it. In this figure, you see a
client accessing a Web site's HTTP server. The HTTP server is
forwarding requests for application resources to one of the application
servers through the use of a proxy server. The application servers are
persisting the session data in an external form.
There are four types of session persistence:
- Memory persistence (one server or a cluster of two or more)
- File system persistence
- Database persistence
- Cookie persistence
Every application server will handle session persistence
differently and all servers may not support all types of persistence.
Objects that are placed in the session must be serializable for
persistence to work.
Memory Persistence
In most cases, a single
standalone server will store sessions in memory. This allows for fast
retrieval and update of the information. It also means that the session
information will be lost when the server is shut down. This is usually
the default configuration on most application servers. Memory
persistence can be used when two or more servers need to share the
session information. The application servers can be configured to share
any changes made to the session so that the information is available on
multiple servers. This redundancy of the session information helps the
cluster preserve the session during a failure.
File System Persistence
File system persistence
can be used to serialize any objects that are in the session. The
object contents are placed in a file on the server. The location of the
files created is configurable; however, the files must be accessible by
all the servers in the cluster. The speed at which the file system is
accessed can be a factor in the performance of your Web site. A slow
disk drive, for example, would result in a delay as data is read from
or written to the file.
Database Persistence
Database persistence can be
used to provide a central data store for the session contents. Each
application server in the cluster must be able to access the database.
When sessions are modified, the changes are immediately persisted in
the database. A data source is usually set up for JDBC persistence and
the connections are pooled. This provides a quicker response. There's
also the issue of database failover, which would be addressed at the
database level of the system.
Cookie Persistence
The fourth type of session
persistence, cookie persistence, is so ineffective and insecure that it
doesn't deserve consideration when designing a fail-safe system. Cookie
persistence, as the name implies, persists session data by storing the
session information in browser cookie(s). There's a limitation on data
handling because cookies store only text, not objects, and the amount
of data that can be transmitted in a cookie is limited. There's also
the fact that cookies transmit data back and forth between the client
and the server. This prevents you (at least it should) from saving
sensitive information, like a social security number. This type of
persistence should be used in only the smallest of Web sites, and only
if there's a good reason not to store the session in memory.
The most common type of persistence is database persistence.
It provides an efficient way of saving session data and it's usually
fairly easy to set up on the application server. Memory persistence in
a cluster is also easy to use, if your application server supports it.
The only drawback is that sessions can sometimes hold large amounts of
data. Storing the session in memory reduces the amount of memory
available to the other processes on the server. File system persistence
can be slow at times and the file system may not always be accessible
to multiple servers.
Watching the Session Size
As you and your
fellow employees work on a Web application, you may notice that more
and more objects are being thrown into the session, often "for
convenience" or "just temporarily." The session becomes a quick
catch-all for any information you need to get from your servlets to
your JSPs. The HttpSession interface makes sessions easy to use, which
can lead to the session being overused. This is a concern because the
session takes up space. In most cases that would be memory space. In
other cases, it could be database or file system space. In all cases,
it means more work for the server and more work for the programmers to
manage what is there.
Although the session is convenient because it's accessible
from every servlet or JSP, it's not always the best place to put
information. Most of the data that's retrieved for display in a Web
application will only be used on one page. Instead of putting the
information into the session scope, use the request scope and then
forward the request from the servlet to the JSP. This causes the
objects to be destroyed after the request has ended, which is after the
data is displayed by the JSP. If you put the objects into the session,
you would either have to remove them in your code or leave them there.
Leaving objects in the session is not a good idea because you're using
up valuable resources for no reason. This becomes even more of an issue
when your Web site has hundreds or thousands of visitors, all of whom
have a session that's loaded with objects.
Some objects should be stored in the session. Objects that may
be needed over and over again as a user moves through a Web site are
those that should be put into the session. Anything that needs to exist
longer than one request can be stored in the session, as long as these
objects are removed as soon as they're no longer needed.
Considerations for Managing Sessions
When working with sessions, there are a few things to consider before designing or redesigning a Web application:
- Are sessions needed in the application?
- How long should the session be inactive before timing out?
- Are all the objects in the session serializable?
- Are the objects being bound to the session too large?
- Do the objects that are in the session really need to be there?
A Need for Sessions
If you have unique
users on a Web site and need to know who they are or need to get
specific information to them, such as search results, then you should
be using sessions. If you follow the guidelines set here, there's no
reason not to use the HttpSession interface that Java provides. It's
easy to use, flexible, secure, and it helps you to build a better Web
site.
There's another architecture that deals with maintaining state for
a client. Instead of relying on the HttpSession interface, state for
clients can be maintained within Enterprise JavaBeans (EJBs). The EJB
architecture takes the business logic for an application and places it
in components or beans. A session bean is a type of EJB that exists for
a given client/server session and provides database access or other
business logic, such as calculations. Session beans can be stateless or
they can maintain the state for a client, very much like an HttpSession
object.
There is still some debate over where the state for a Web site
visitor should be maintained. The best design for the application at
this time is to continue using the HttpSession object for maintaining
the state of the presentation layer of the Web application and to use
stateful EJBs to maintain the state of the business logic and data
layer. There are many other factors that should be considered with
EJBs, one being the better performance of stateless beans over those
that maintain state. These issues, which are outside the scope of this
article, should be considered carefully when architecting an
application.
Session Timeout
By default, on most servers
the session is set to expire after 30 minutes of inactivity. The amount
of time can be configured in the deployment descriptor of the Web
application. The HttpSession API also provides a
setMaxInactiveInterval() method that you can use to specify the timeout
period for a session. The getMaxInactiveInterval() method will return
this timeout value. The value given is in seconds.
The length of time will vary depending on what your visitors
are doing on your site. If they're logging in to check their account
balance, a shorter session timeout period can be used because it
doesn't take long for a person to read a couple of numbers. If, on the
other hand, the user is logging in to read large amounts of data, you
need to be sure that you provide enough time for the user to do what he
or she wants without being logged out. If the user is constantly
navigating through your site, the session will last indefinitely.
Implement Serializable
It's important to
make sure that all objects placed in the session can be serialized.
This may not be an issue if you know that your Web application will not
run in a cluster, but it should still be done anyway. What happens if
your Web site grows too big for one server and you suddenly have to
move to two? If you implement Serializable in your code now, you won't
have to go back and do it later.
Keep It Simple
You should design objects
that are going to be placed into a session so that they're not too big
and don't contain unnecessary information. A JavaBean that contains a
customer's name, address, phone number, e-mail address, credit card
numbers, and order history should not be placed into the session if
you're only going to use the object to get the customer's name.
Session Contents
When you're working on a
Web site, it's important to know which objects are in the session and
why they're needed. The size of the session should be kept as small as
possible. If you're building a new Web site, work out ahead of time
what goes in the session, why it's there, and where it gets removed. If
you're redesigning an existing site, this may be a little tougher,
especially when you have hundreds of servlets and JSPs to deal with. In
this case, try implementing an HttpSessionAttributeListener to get an
idea of what is going into the session. With this information, you may
be able to better manage your sessions.
Conclusion
Hopefully this article helped
you to better understand the design issues involved in using the
HttpSession interface. Java provides a more robust session
implementation than other languages. It's because of this power and
flexibility that you must take the time to properly lay out the use of
the session. A well-designed session will help make a Web application
better for the programmers and the users.
References
Hall, M. (2002). More Servlets and JavaServer Pages. Prentice Hall PTR.
Java Servlet Technology:
http://java.sun.com/products/servlet
Enterprise JavaBeans Technology:
http://java.sun.com/products/ejb
Java BluePrints (J2EE):
http://java.sun.com/blueprints/guidelines/
designing_enterprise_applications
另外,還有一些收集的材料
關(guān)于HttpSession的誤解實(shí)在是太多了,本來(lái)是一個(gè)很簡(jiǎn)單的問(wèn)題,怎會(huì)搞的如此的復(fù)雜呢?下面說(shuō)說(shuō)我的理解吧:
1、HTTP協(xié)議本身是“連接-請(qǐng)求-應(yīng)答-關(guān)閉連接”模式的,是一種無(wú)狀態(tài)協(xié)議(HTTP只是一個(gè)傳輸協(xié)議);
2、Cookie規(guī)范是為了給HTTP增加狀態(tài)跟蹤用的(如果要精確把握,建議仔細(xì)閱讀一下相關(guān)的RFC),但不是唯一的手段;
3、所謂Session,指的是客戶端和服務(wù)端之間的一段交互過(guò)程的狀態(tài)信息(數(shù)據(jù));這個(gè)狀態(tài)如何界定,生命期有多長(zhǎng),這是應(yīng)用本身的事情;
4、由于B/S計(jì)算模型中計(jì)算是在服務(wù)器端完成的,客戶端只有簡(jiǎn)單的顯示邏輯,所以,Session數(shù)據(jù)對(duì)客戶端應(yīng)該是透明的不可理解的并且應(yīng)該受控于服務(wù)端;Session數(shù)據(jù)要么保存到服務(wù)端(HttpSession),要么在客戶端和服務(wù)端之間傳遞(Cookie或url rewritting或Hidden input);
5、由于HTTP本身的無(wú)狀態(tài)性,服務(wù)端無(wú)法知道客戶端相繼發(fā)來(lái)的請(qǐng)求是來(lái)自一個(gè)客戶的,所以,當(dāng)使用服務(wù)端HttpSession存儲(chǔ)會(huì)話數(shù)據(jù)的時(shí)候客戶端的每個(gè)請(qǐng)求都應(yīng)該包含一個(gè)session的標(biāo)識(shí)(sid, jsessionid 等等)來(lái)告訴服務(wù)端;
6、會(huì)話數(shù)據(jù)保存在服務(wù)端(如HttpSession)的好處是減少了HTTP請(qǐng)求的長(zhǎng)度,提高了網(wǎng)絡(luò)傳輸效率;客戶端session信息存儲(chǔ)則相反;
7、客戶端Session存儲(chǔ)只有一個(gè)辦法:cookie(url rewritting和hidden input因?yàn)闊o(wú)法做到持久化,不算,只能作為交換session id的方式,即a method of session tracking),而服務(wù)端做法大致也是一個(gè)道理:容器有個(gè)session管理器(如tomcat的 org.apache.catalina.session包里面的類),提供session的生命周期和持久化管理并提供訪問(wèn)session數(shù)據(jù)的 api;
8、使用服務(wù)端還是客戶端session存儲(chǔ)要看應(yīng)用的實(shí)際情況的。一般來(lái)說(shuō)不要求用戶注冊(cè)登錄的公共服務(wù)系統(tǒng)(如google)采用 cookie做客戶端session存儲(chǔ)(如google的用戶偏好設(shè)置),而有用戶管理的系統(tǒng)則使用服務(wù)端存儲(chǔ)。原因很顯然:無(wú)需用戶登錄的系統(tǒng)唯一能夠標(biāo)識(shí)用戶的就是用戶的電腦,換一臺(tái)機(jī)器就不知道誰(shuí)是誰(shuí)了,服務(wù)端session存儲(chǔ)根本不管用;而有用戶管理的系統(tǒng)則可以通過(guò)用戶id來(lái)管理用戶個(gè)人數(shù)據(jù),從而提供任意復(fù)雜的個(gè)性化服務(wù);
9、客戶端和服務(wù)端的session存儲(chǔ)在性能、安全性、跨站能力、編程方便性等方面都有一定的區(qū)別,而且優(yōu)劣并非絕對(duì)(譬如TheServerSide號(hào)稱不使用HttpSession,所以性能好,這很顯然:一個(gè)具有上億的訪問(wèn)用戶的系統(tǒng),要在服務(wù)端數(shù)據(jù)庫(kù)中檢索出用戶的偏好信息顯然是低效的,Session管理器不管用什么數(shù)據(jù)結(jié)構(gòu)和算法都要耗費(fèi)大量?jī)?nèi)存和CPU時(shí)間;而用cookie,則根本不用檢索和維護(hù)session數(shù)據(jù),服務(wù)器可以做成無(wú)狀態(tài)的,當(dāng)然高效);
reply1:
不過(guò)我們也不能在session里面放入過(guò)多的東西
一般來(lái)說(shuō)不能超過(guò)4K
太多了
對(duì)系統(tǒng)資源是一個(gè)很嚴(yán)重的浪費(fèi)
reply2:
4K已是很大的一個(gè)數(shù)字了。
我一般喜歡寫一個(gè)類。封裝用戶登陸后的一些信息。
然后把這個(gè)類放在session中,取得直接用類的方法取相關(guān)信息,
最近接到兩個(gè)很小的tickets,兩個(gè)都是為了項(xiàng)目開(kāi)發(fā)時(shí)的方便:一是將logs寫入到數(shù)據(jù)庫(kù)中,以方便日志的查詢;一是在build時(shí),在war包加入svn revision info。
1) logging to database
經(jīng)過(guò)調(diào)查,決定采用log4j的org.apache.log4j.jdbc.JDBCAppender,于是采用:
# logging to db
log4j.logger.com.example=DEBUG, DATABASE
log4j.additivity.com.example=false
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.url=jdbc:postgresql://localhost:5432/test
log4j.appender.DATABASE.driver=org.postgresql.Driver
log4j.appender.DATABASE.user=pguser
log4j.appender.DATABASE.password=post
log4j.appender.DATABASE.sql=INSERT INTO debug_log(created, logger, priority, message) VALUES (to_timestamp('%d{ISO8601}','YYYY-MM-DD HH:MI:SS.MS'),'%c.%M:%L','%p','%m')
log4j.appender.DB.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=%d{ISO8601} %p %c.%M:%L %m
很直觀,用起來(lái)還很方便,但是不久就出現(xiàn)了問(wèn)題,tomcat拋出了exception。只好把之前fixed ticket reopen,提交新的comments:Unfortunately, org.apache.log4j.jdbc.JDBCAppender that ships with the Log4j distribution is not able to process logging messages that have characters like ' (single quote) and , (comma) in it. When logging messages contains characters like single quote or comma, the program will throw an exception.
重新google了,找到了一個(gè)plusjdbc,Looking further, I found an alternative JDBCAppender package (org.apache.log4j.jdbcplus.JDBCAppender) from http://www.dankomannhaupt.de/projects/index.html. It can solve this problem. 長(zhǎng)嘆了一下。
最后采用:
log4j.appender.DATABASE=org.apache.log4j.jdbcplus.JDBCAppender
log4j.appender.DATABASE.url=jdbc:postgresql://localhost:5432/test
log4j.appender.DATABASE.dbclass=org.postgresql.Driver
log4j.appender.DATABASE.username=pguser
log4j.appender.DATABASE.password=post
log4j.appender.DATABASE.sql=INSERT INTO debug_log(created, logger, priority, message) VALUES (to_timestamp('@LAYOUT:1@', 'YYYY-MM-DD HH:MI:SS.MS'),'@LAYOUT:3@','@LAYOUT:2@','@LAYOUT:4@')
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=%d{ISO8601}#%p#%c.%M:%L#%m
log4j.appender.DATABASE.layoutPartsDelimiter=#
log4j.appender.DATABASE.buffer=1
log4j.appender.DATABASE.commit=true
log4j.appender.DATABASE.quoteReplace=true
問(wèn)題解決,但是中間有點(diǎn)小波折,在我的項(xiàng)目中,log4j.jar(>1.2.9)重復(fù)了,在$CATALINA_HOME/lib下有一份,在web工程下的WEB-INF/lib下也有一份,而plus-jdbc.jar放置在$CATALINA_HOME/lib下,結(jié)果啟動(dòng)Tomcat,出現(xiàn)
log4j:ERROR A "org.apache.log4j.jdbcplus.JDBCAppender" object is not assignable to a "org.apache.log4j.Appender" variable.
log4j:ERROR The class "org.apache.log4j.Appender" was loaded by
log4j:ERROR [WebappClassLoader^M
delegate: false^M
repositories:^M
----------> Parent Classloader:^M
org.apache.catalina.loader.StandardClassLoader@1ccb029^M
] whereas object of type
log4j:ERROR "org.apache.log4j.jdbcplus.JDBCAppender" was loaded by [org.apache.catalina.loader.StandardClassLoader@1ccb029].
log4j:ERROR Could not instantiate appender named "DATABASE".
原來(lái)是兩個(gè)JDBCAppender實(shí)例不在同一個(gè)classlaoder里面,將WEB-INF/lib下的log4j.jar刪除掉,重啟就沒(méi)問(wèn)題了,按理,將$CATALINA_HOME/lib下的plus-jdbc.jar移到WEB-INF/lib下,應(yīng)該也沒(méi)問(wèn)題,沒(méi)有測(cè)試。
2)Add build revision info in war file and read it on tomcat startup
這個(gè)經(jīng)歷比較慘痛,兩個(gè)問(wèn)題,如何獲取revision? And how to read it when tomcat startup? 第二個(gè)問(wèn)題倒是沒(méi)什么,采用javax.servlet.ServletContextListener就可以實(shí)現(xiàn),很簡(jiǎn)單,走彎路的是第一個(gè)問(wèn)題,google后發(fā)現(xiàn)有兩種常見(jiàn)的實(shí)現(xiàn):
As I have learned, there are totally two solutions to get svn revision info.
First, retrieve the svn revision from local file($BASE_HOME/.svn/entries). Just parsing the xml file, get the revision property and write it to a properties file.(就是該死的xml,遠(yuǎn)在烏克蘭的同事,該文件卻不是xml的,也只怪自己調(diào)研不充分,還得折騰了半天,后來(lái)發(fā)現(xiàn),最新版的svn為了performance的考慮,采用meta data來(lái)實(shí)現(xiàn)entries)
Second, retrieve the svn revision from the remote repository. The solution always use a svn client to perform a demand with remote server to retrieve the revision info. Installing a snv client and using SvnAnt? are most commonly used at present. SvnAnt? is an ant task that provides an interface to Subversion revision control system and encapsulates the svn client. It uses javahl - a native (JNI) java interface for the subversion api if it can find the corresponding library. javahl is platform-dependent.
Because of needing interaction with the server(服務(wù)器在國(guó)外,更新很慢), now I employ the first solution. But I found a flaw of this method when i was going off duty. Generally, we may update our project with svn before committing. This may make a mismatch with svn revision between remote server and local file. Svn revision in local file is usually updated when we update our project. But when we take a commit after update, the svn revision in the remote server will change to a new one.
So, the case is that if we update, commit, and then build, we may get a mismatch with the newest svn revision, and build the error revision into our ROOT.war. If we update , then build ,without commit, we can get right revision info.
下面是第一版實(shí)現(xiàn):
<!-- retrieve the svn revision from the remote repository
<path id="svnant.lib" >
<fileset dir="${lib.dir}">
<include name="svnant.jar"/>
<include name="svnClientAdapter.jar"/>
<include name="svnjavahl.jar"/>
</fileset>
</path>
<taskdef name="svn" classpathref="svnant.lib" classname="org.tigris.subversion.svnant.SvnTask" />
<target name="get-svn-revision">
<svn username="*******" password="******" javahl="true">
<status urlProperty="https://example.com" path="." revisionProperty="svn.revision" />
</svn>
<echo>svn revision: ${svn.revision}</echo>
</target>
-->
<!-- retrieve the svn revision from local file(.svn/entries). The file may contain several 'wc-entries.entry.revision' elements.
The property will get several values seperated by ',' when using xmlproperty task. Then the svn revison expected will be the
max one of these property values.
-->
<property name="svn.revision.file" value=".svn/entries" />
<!-- This property is used to run xmlproperty task successfully with a low version of svn client (under 1.3.1). Don't sure whether it really makes sense -->
<property name="build.id" value="foo" />
<target name="get-svn-revision">
<xmlproperty file="${svn.revision.file}" collapseAttributes="true"/>
<echo>svn revision: ${wc-entries.entry.revision}</echo>
</target>
<!--
If the file doesn't contain any 'wc-entries.entry.revision' element, the content of the property file will be: revision = ${wc-entries.entry.revision};
If contain a 'wc-entries.entry.revision' element, mark this value as $revision_value, then the content will be: revision = $revision_value;
If contain several 'wc-entries.entry.revision' elements, mark these values as $value1, $value2, ..., respectively, then the content will be: revision = $value1,$value2,..., seperated by a ',';
-->
<property name="svn.revision.propertyfile" value="${build.dir}/revision.properties" />
<target name="write-svn-revision-to-file" depends="get-svn-revision">
<delete file="${svn.revision.propertyfile}"/>
<propertyfile file="${svn.revision.propertyfile}" comment="record svn revision">
<entry key="revision" value="${wc-entries.entry.revision}"/>
</propertyfile>
</target>
結(jié)果write-svn-revision-to-file這個(gè)在我這倒是可以獲取本地的svn revision,但是遠(yuǎn)方的同事可急了,build老失敗,只好把這部分build注釋了,還好,到周末了,可以在家好好研究一下,很快找了一個(gè)新的工具:
It's my fault. In my version of svn, the entries file is xml formatted. So i parse it using ant task - 'xmlproperty'. Now i have fix this problem by using 'svnkit' tools, a pure java svn toolkit. Now there are two ways to retrieve svn revision. One is from remote repository server. For this one, before building, you should set your own username and password for the remote repository server('remote.repository.username' and 'remote.repository.password' properties in build.xml,respectively). Another one is retrieving revision from local working copy. If using this one, you should set 'local.repository' property in build.xml to your own directory.
利用svnkit,從服務(wù)器上獲取revision大概是:
repository = SVNRepositoryFactory.create(SVNURL.parseURIDecoded(urlStr));
ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(username, password);
repository.setAuthenticationManager(authManager);
headRevision = repository.getLatestRevision();
從本地working copy獲取revision:
SVNClientManager clientManager = SVNClientManager.newInstance();
SVNWCClient wcClient = clientManager.getWCClient();
SVNInfo info = wcClient.doInfo(new File(fileUrl), SVNRevision.WORKING);
headRevision = info.getRevision().getNumber();
利用ant task將獲取的revision寫入到一個(gè)配置文件中(如revision.properties),在tomcat啟動(dòng)的時(shí)候加載進(jìn)來(lái),就可以了。