Original link: http://haohaoxuexi.iteye.com/blog/1983532
Spring provides a jmstransactionmanager for transaction management of JMS ConnectionFactory. This will allow the JMS application to take advantage of Spring's transaction management features. Jmstransactionmanager binds a connectionfactory/session such as a pairing to a thread from the specified connectionfactory when performing local resource transaction management. Jmstemplate automatically detects such transactional resources and operates on them accordingly.
In a Java EE environment, connectionfactory pools connection and sessions so that these resources are effectively reused throughout the transaction. In a stand-alone environment, when you use spring's singleconnectionfactory, all transactions will have a connection, but each transaction will retain its own independent session.
Jmstemplate can handle distributed transactions using Jtatransactionmanager and the ability to distribute JMS connectionfactory.
In the spring integrated JMS application, if we want to do local transaction management, it is very simple to specify its Sessiontransacted property to True when defining the corresponding message listener container, such as:
<BeanID= "Jmscontainer"class= "Org.springframework.jms.listener.DefaultMessageListenerContainer"> < Propertyname= "ConnectionFactory"ref= "ConnectionFactory" /> < Propertyname= "Destination"ref= "Queuedestination" /> < Propertyname= "MessageListener"ref= "Consumermessagelistener" /> < Propertyname= "sessiontransacted"value= "true"/> </Bean>
The property value defaults to False, so that JMS performs transaction control while the message is being monitored, and JMS will rollback the received message when the listener fails to receive the message. The Sessionawaremessagelistener is also under the same transaction when sending a return message after receiving a message, but for other operations, such as database access, will not be part of the transaction control.
Here we can do a test like this: We are configured to listen on the queuedestination message in the Sessiontransacted property of the listener container is true, Then change the message listener Consumermessagelistener we mentioned earlier to this:
Public classConsumermessagelistenerImplementsMessageListener { Public voidonMessage (Message message) {//here we know that the producer is sending a plain text message, so you can either cast directly or change the parameters of the OnMessage method to the subclass of message TextMessageTextMessage textmsg =(textmessage) message; System.out.println ("Received a plain text message. "); Try{System.out.println ("Message content is:" +Textmsg.gettext ()); if(1 = = 1) { Throw NewRuntimeException ("Error"); } } Catch(jmsexception e) {e.printstacktrace (); } } }
We can see that in the above code our Consumermessagelistener throws a runtimeexception when it receives the message, according to what we said above, Because we have defined its Sessiontransacted property to true on the corresponding listener, JMS will rollback the received message when it throws an exception, that is, the message will still be received the next time the message is received.
To verify this, we first execute the test code, send a text message to Queuedestination, and this time Consumermessagelistener will throw a runtimeexception when it is received. A plain text message that has been received will be rolled back, and then we get rid of the statement in the code above that throws an exception, that is, Consumermessagelistener can receive the message normally, and then we run the test code again, Send a message to Consumermessagelistener listening queuedestination. If the message that threw an exception before the takeover was already rolled back, then this time will be able to receive two messages, the console will output the contents of the two messages received. Specific results interested friends can be verified by themselves.
If you want to receive messages and database access in the same transaction, then we can configure an external transaction management to configure a message monitoring container (such as defaultmessagelistenercontainer) that supports external transaction management. To configure a message listener that participates in distributed transaction management, we can configure a jtatransactionmanager, and of course the underlying JMS connectionfactory needs to be able to support distributed transaction management, and to register our Jtatransactionmanager correctly. In this way, the message listener receives the message and the corresponding database access is under the same database control, and the transaction rollback occurs when the message receives a failure or the database access fails.
<BeanID= "Jmscontainer"class= "Org.springframework.jms.listener.DefaultMessageListenerContainer"> < Propertyname= "ConnectionFactory"ref= "ConnectionFactory" /> < Propertyname= "Destination"ref= "Queuedestination" /> < Propertyname= "MessageListener"ref= "Consumermessagelistener" /> < Propertyname= "TransactionManager"ref= "Jtatransactionmanager"/> </Bean> <BeanID= "Jtatransactionmanager"class= "Org.springframework.transaction.jta.JtaTransactionManager"/>
When TransactionManager is specified for a message listening container, the message listener container ignores the value of sessiontransacted.
We can also do an experiment here about using Jtatransactionmanager to manage the above distributed transactions.
First: Add the following configuration to spring configuration file Applicationcontext.xml:
<BeanID= "JdbcTemplate"class= "Org.springframework.jdbc.core.JdbcTemplate"> < Propertyname= "DataSource"ref= "DataSource"/> </Bean> <Jee:jndi-lookupJndi-name= "Jdbc/mysql"ID= "DataSource"/> <BeanID= "Jtatransactionmanager"class= "Org.springframework.transaction.jta.JtaTransactionManager"/> <Tx:annotation-drivenTransaction-manager= "Jtatransactionmanager"/>
As we can see, here we introduce a JNDI data source, define a jtatransactionmanager, define spring annotation-based declarative transaction management, Defines a spring-provided tool class JdbcTemplate for JDBC operations.
Next, change our consumermessagelistener to the following form:
Public classConsumermessagelistenerImplementsMessageListener {@AutowiredPrivateTestdao Testdao; Private intCount = 0; Public voidonMessage (Message message) {//here we know that the producer is sending a plain text message, so you can either cast directly or change the parameters of the OnMessage method to the subclass of message TextMessageTextMessage textmsg =(textmessage) message; System.out.println (NewDate (). toLocaleString () + "received a plain text message. "); Try{String text=Textmsg.gettext (); System.out.println ("Message content is:" +text); System.out.println ("The value of the current count is:" +count); Testdao.insert (Text+count); if(count = = 0) {Count++; Throw NewRuntimeException ("error! Something went wrong! "); } } Catch(jmsexception e) {e.printstacktrace (); } } }
As we can see, in Consumermessagelistener We define an instance variable count with an initial value of 0, and in OnMessage we can see that we call the Insert method of Testdao with the received message as a parameter When the count value is 0, that is, when the first message is received, the value of Count is added 1, and a run-time exception is thrown. So what we're going to test here is the first time Testdao has inserted the relevant content into the database, and then in the OnMessage throws an exception at the same time count plus 1, we expect the result should be the database rollback, and JMS also rollback, So JMS will continue to try to receive the message, at which point the Insert method of Testdao is also called to insert the content into the database, then count is no longer 0, so no exception is thrown at this time, and JMS successfully receives the message. Testdao also successfully inserted the message content into the database. To prove this expectation we can look at the output of the console in addition to the data inserted in the database, and the console will output two messages received, and the first time count is 0 and the second count is 1.
Testdao is an interface whose testdaoimpl approach to insert is implemented as follows:
@Transactional (readonly=false) publicvoid Insert (final String name { jdbctemplate.update ("INSERT INTO Test (?)") , name); }
Here we use the Jtatransactionmanager -enabled Weblogic for testing because it is a Web container, so we define a Controller here to send the message, the code is as follows:
@Controller @requestmapping ("Test") Public classTestController {@Autowired @Qualifier ("Queuedestination") PrivateDestination Destination; @AutowiredPrivateProducerservice Producerservice; @RequestMapping ("First") PublicString First () {Producerservice.sendmessage (destination,"Hello, now is:" +NewDate (). toLocaleString ()); return"/test/first"; } }
The next step is to enable the WebLogic server, enter its console, define a JNDI data source called "Jdbc/mysql", and then deploy the project to the WebLogic server and start it. Next we can access/test/first.do access to the first method above. The console will then output the following information:
We can see that when count is 0, it is received once, and then an exception is thrown, and then Count is 1 and then received again , indicating that the count is 0 When the exception is thrown and our JMS is rolled back, does our database have to be rolled back? We then look at the contents of the database:
We can see that there is only one record in the database table, and the last one represents the value of Count 1, which means that our database is also rolled back when the message received by JMS throws an exception. On the issue of distributed transaction Management using Jtatransactionmanager this is where interested friends can experiment.
Spring consolidates JMS (iv)--transaction management