Spring MVC JDBC DataSourceTransactionManager: Data committed even after readonly=true - spring-mvc

I am currently developing a Spring MVC application.I have configured a JDBC TransactionManager and I am doing declarative transaction management using AOP XML.However, even if I configure the method to run on a read-only=true, it still commits the transaction.
Database : Oracle 10g
My database-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schem...ring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="defaultAutoCommit" value="false" />
</bean>
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:com/mybatis/mappers/*.xml" />
</bean>
<!--
the transactional advice (what 'happens'; see the <aop:advisor/> bean
below)
-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true" />
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*" read-only="true" rollback-for="RuntimeException"/>
</tx:attributes>
</tx:advice>
<!--
ensure that the above transactional advice runs for any execution of
an operation defined by the FooService interface
-->
<aop:config>
<aop:pointcut id="fooServiceOperation"
expression="execution(* com.service.EmployeeService.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation" />
</aop:config>
</beans>
My controller
package com.service;
import java.util.List;
import com.mybatis.dao.EmployeeMapperInterface;
import com.spring.model.Employee;
public class EmployeeService implements EmployeeBaseService{
EmployeeMapperInterface employeeMapper;
public EmployeeMapperInterface getEmployeeMapper() {
return employeeMapper;
}
public void setEmployeeMapper(EmployeeMapperInterface employeeMapper) {
this.employeeMapper = employeeMapper;
}
#Override
public Employee getEmployeeById(long empId){
//retrieve from database
List empList = employeeMapper.getEmployeeWithId(empId);
if(empList != null && empList.size()>0){
return (Employee) empList.get(0);
}
return null;
}
#Override
public long saveEmployee(Employee employee){
long empId = 0l;
if(employee.getEmpId()==0){
empId = new Long( employeeMapper.insertEmployee(employee));
}else{
employeeMapper.updateEmployee(employee);
empId = employee.getEmpId();
}
try {
System.out.println("gonna sleep");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return empId;
}
How do I prevent the auto commit?I have also noticed that even if I don't put any transaction management code, the code still commits. Note, the transaction advice is however,invoked as when I put a no-rollback-for for RuntimeException and then do a 1/0, it correctly commits the data and rolls back if I put the same as rollback-for.
I have also tried out the query timeout by putting the thread on sleep, even that doesn't work, but I figure that timeout might be for an actual query, so thats fine.
Thanks in advance!

The advice read-only is only advice. It is not a requirement that the underlying transaction management system prevent writes when something is marked read-only, it is meant more as an optimization hint, saying that this method is read only, so you don't need to worry about it changing things. Some transaction managers will complain if changes are made in a read-only transaction, some will not. Generally, datasources acquired via JNDI will not. In any case, you should not rely on read-only advice preventing changes from being written back to disk.
Your options for preventing changes from being persisted are:
Mark the transaction rollback only or throw an exception having the same effect
Detach/evict the object from the transaction session before you change it
Clone the object and use the clone

DataSourceTransactionManager begins transaction with doBegin method.
From this method DataSourceUtils.prepareConnectionForTransaction called.
Inside this method you can see following code block:
if (definition != null && definition.isReadOnly()) {
try {
if (logger.isDebugEnabled()) {
logger.debug("Setting JDBC Connection [" + con + "] read-only");
}
con.setReadOnly(true);
}
catch (SQLException ex) {
So you could configure your logging framework to set log-level to DEBUG for DataSourceUtils class.
Or you could set breakpoint in this place and debug manually.
According to this article I expect that SET TRANSACTION READ ONLY will be executed on your Oracle connection.
And from Oracle docs we could see benefits which you receive in case of success:
By default, the consistency model for Oracle guarantees statement-level read consistency, but does not guarantee transaction-level read consistency (repeatable reads). If you want transaction-level read consistency, and if your transaction does not require updates, then you can specify a read-only transaction. After indicating that your transaction is read-only, you can execute as many queries as you like against any database table, knowing that the results of each query in the read-only transaction are consistent with respect to a single point in time.

The read-only behaviour is strictly driver specific. Oracle driver ignores this flag entirely. For instance the same update statements executed in Oracle will modify the database if run in read-only transaction, while in HSQL2 I was getting db level exceptions.
I know no other way than explicit rollback through api or exception to prevent commit in Oracle. Also this way your code will be portable between different drivers and databases.

The answer is on Spring MVC Mybatis transaction commit
Detailed stack traces are also available.
To summarize,
Read-only is only an advice and it guarantees nothing, and I would
really like the Spring docs to be updated about this.
whenever a query is executed in Oracle using Mybatis, it is in the context of a transaction which is automatically started,
committed(or rolled back, if execption is raised),and closed by
Mybatis.
Logging the application was a good idea and it helped me to find out how the actual transactions are started etc
.

Related

Spring MVC Converters doesn't work at all

I'm very new to Spring MVC and Java EE at all (I came from PHP+Zend2). My english is poor too. I use NetBeans.
My problem is that my custom converter does not work. Here's some code:
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<bean id="universalDAO" class="dao.UniversalDAO"/>
<bean id="sessionManager" class="utils.SessionManager"/>
<bean id="idToEntityConverterFactory" class="utils.IdToEntityConverterFactory">
<property name="dao" ref="universalDAO"/>
</bean>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<ref bean="idToEntityConverterFactory" />
<bean id="temp" class="utils.TempConverter" />
</list>
</property>
</bean>
<bean name="universalService" class="service.UniversalService">
<property name="universalDAO" ref="universalDAO"/>
</bean>
<bean name="sessionApplicationService" class="service.SessionApplicationService">
<property name="universalDAO" ref="universalDAO"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>
<bean name="systemUserApplicationService" class="service.SystemUserApplicationService">
<property name="universalDAO" ref="universalDAO"/>
</bean>
<aop:aspectj-autoproxy />
<bean id="loggerAspect" class="aspect.LoggerAspect"/>
</beans>
I also have tried version with:
class="org.springframework.format.support.FormattingConversionServiceFactoryBean"
IdToEntityConverterFactory is a ConverterFactory created with this tutorial but it is not important now. I wrote simpler one not to do mess.
TempConverter.java
package utils;
import entity.Role;
import org.springframework.core.convert.converter.Converter;
public class TempConverter implements Converter<String, Role> {
#Override
public Role convert(String id) {
return new Role();
}
}
Here is .jsp fragment:
<form:select path="${names[item.index]}" items="${valueOptions[names[item.index]]}" />
When I submit the form there appears an error:
Failed to convert property value of type java.lang.String[] to required type java.util.List for property roleList; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [entity.Role] for property roleList[0]: no matching editors or conversion strategy found
I found solution to similar problem here . It has something to do with
<mvc:annotation-driven>
but I don't use such tag anywhere in my application (should I?).
My question is how to make any converter work while binding form data to Java object.
EDIT:
I figured out some workaround. I have overriden initBinder method in my Controller:
#Override
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
binder.setConversionService(conversionService);
}
conversionService had to be previously set in Controller of course:
private ConversionService conversionService;
//...
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
dispatcher-servlet.xml:
<bean class="controller.SystemUserFormController" p:applicationService-ref="systemUserApplicationService" p:sessionManager-ref="sessionManager" p:conversionService-ref="conversionService" />
It works now but it is kind of inconvenience because:
I have to add extra code p:conversionService-ref="conversionService" in every Controller I need converter to be used.
It works out-of-the-box in every toutorial I found on the internet but not for me. I am just curious what am I doing different.
Kindest regards!
Your questions:
I have to add extra code p:conversionService-ref="conversionService" in every Controller I need converter to be used.
You can use #Autowired to inject ConversionService.
You can implement common parent class for your controllers with #InitBinder
You can use abstract parent bean definition <bean abstract="true" ...>
It works out-of-the-box in every toutorial I found on the internet but not for me. I am just curious what am I doing different.
Just use <mvc:annotation-driven>. This easy-to-use configuration is there so that you don't need to configure stuff manually.
How to do it
You can implement WebBindingInitializer. This bean needs to be set up on handler adapter.
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="webBindingInitializer" ref="yourBindingInitializer" />
</bean>
However this approach is kind of painful if you are not already defining hanlder adapter yourself. When you define this bean it disables some DispatcherServlet's default behavior. So you might need to do a bit more than to define this bean.
Off-topic advice
Problem with Spring is that the internet is full of obsolete tutorials. Please use the official guide and reference app. Start using namespace (or even Java) config, autowiring, #Controller components and #RequestMapping.
I belive there is not such think as mvc:annotation-config. There are 2 other things:
context:annotation-config
mvc:annotation-driven
Please tell me if I am wrong
I have tried both and both doesn't work. Here's what what have I done:
Removed p:conversionService-ref="conversionService" from my Controller bean
Added #Autowired annotation to my setter
#Autowired
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
Added context:annotation-config/ (or mvc:annotation-driven/) to applicationContext.xml
Unfortunately setter has never been executed!
My source is here
Quote: "When Spring finds an #Autowired annotation used with setter methods, it tries to perform byType autowiring on the method."
I also have tried using setter with exactly the same type as bean class - still nothing.

Debug spring mvc view rendering time

I am currently using Spring MVC 3.x,
and using the freemarker view resolver.
Recently i have been wondering about the execution time that it takes for a view to translate into html before getting sent back as a response. I would like to do tunings if things are slow in this area, which is why i need some numbers.
In plain freemarker mode, i can actually do the simple System.currentTimeMillis() between these to find out the execution time :
long start = System.currentTimeMillis();
// this could be slow or fast depending on the caching used
Template temp = cfg.getTemplate(ftlName);
...
temp.process(model, myWriter); // depends on the writer
System.out.printf("done in %s ms", System.currentTimeMillis() - start);
But how do i do this when with spring mvc's freemaker view rendering ?
You might consider extending org.springframework.web.servlet.view.freemarker.FreeMarkerView and configuring FreeMarkerViewResolver with your custom logging view implementation.
Logging view implementation could look like this:
public class LoggingFreeMarkerView extends FreeMarkerView {
private static final transient Log log = LogFactory.getLog(LoggingFreeMarkerView.class);
#Override
protected void doRender(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
long start = System.currentTimeMillis();
super.doRender(model, request, response);
log.debug("Freemarker rendered " + request.getRequestURI() + " in " + (System.currentTimeMillis() - start) + " ms");
}
}
And wire the view resolver with new class:
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver" autowire="no">
<property name="viewClass" value="com.example.LoggingFreeMarkerView" />
<property name="cache" value="false" /> <!-- cache disabled for performance monitoring -->
<property name="prefix" value="" />
<property name="suffix" value=".ftl" />
<property name="contentType" value="text/html;charset=utf-8" />
<property name="exposeRequestAttributes" value="true" />
<property name="requestContextAttribute" value="base" />
</bean>
You are going to calculate just on server side merging template with data,Main problem is when freemarker executing on page ,As you know freemarker built on top of jsp page so you should bring code to jsp side to calculate execution time,
As my experience according to data size load time in freemarker is different.
if else condition also is too slow compare to jstl!
I can recommend thymeleaf for spring that allowing templates to be working prototypes on not xml style .

my ConversionService encountered 500 error code

I am learning the spring mvc and when i try to use the ConversionService ,i encounterrd 500
#RequestMapping("/handle81")
public String handle81(#RequestParam("user")User user,ModelMap modelMap) {
System.out.println(user);
modelMap.put("user", user);
return "/user/success";
}
this is the handler method ,i've put the #RequestMapping("/user") at the class
and the converter
public class StringToUserConverter implements Converter<String, User> {
public User convert(String source) {
System.out.println(source);
User user=new User();
String[] item=source.split(":");
user.setUserName(item[0]);
user.setPassword(item[1]);
user.setName(item[2]);
return user;
}
}
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.baobaotao.domain.StringToUserConverter" />
</list>
</property>
</bean>
so when i browse
http://localhost:8080/spring-mvc/user/handle81.html?user=asdf:asdf:fdas
it gets 500 and prints nothing at the console(i use maven-jetty to do the test)
thx for helping~
I think your Request URL may not be not matching. You specify "/handle81" in the annotation, but are requesting "/handle81.html".
It's hard to tell, without further information, whether the problem is matching & dispatching the request to the handler; or in the conversion.
Try another handler with the parameter of type String, and see whether you can call that successfully. At least you'll then know where the problem is.
And what is the exception stack-trace? Why didn't you post it? That's your most important clue & you should always post the ex message & top few lines/ where it was thrown, when you ask a question. It should be in either the application or Tomcat/ other server logs.

Why isn't this code writing log output on my Tridion Content Delivery server?

I have a user control rendering content from a custom (non-Tridion) database. The connection string for this custom database is incorrect, and so I'm getting an SqlException when the code tries to connect.
My code is currently:
var logger =
Tridion.ContentDelivery.Web.Utilities
.LoggerFactory.GetLogger(this.GetType().ToString());
try
{
/* make a database connection - failing with SqlException */
}
catch (SqlException e)
{
logger.Error("Could not connect to database: " + e.ToString());
}
My \bin\config\logback.xml file contains:
<property name="log.pattern" value="%date %-5level %logger{0} - %message%n"/>
<property name="log.history" value="7"/>
<property name="log.folder" value="c:/tridion/log"/>
<property name="log.level" value="DEBUG"/>
...
<appender name="rollingCoreLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.folder}/cd_core.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>${log.history}</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<prudent>true</prudent>
</appender>
...
<root level="warn">
<appender-ref ref="rollingCoreLog"/>
</root>
There's a stack of logs in C:\Tridion\log, but the most recently changed one was modified 20 minutes ago, and doesn't contain my log message text (I just did a search for "database" in notepad).
Why isn't my log output being sent to the log?
You have 2 options:
You define the root logging to "DEBUG" but this has the disadvantage that allows a huge amount of logging from all the third-party libraries that Tridion is using. Here is the snippet: <root level="DEBUG"> <appender-ref ref="rollingCoreLog"/> </root>
You define a special appender to include also the Tridion .NET logging: <logger name="Tridion.ContentDelivery" level="${log.level}"><appender-ref ref="rollingCoreLog"/></logger>
Note that in the second case you need your logger to be bound to a namespace under Tridion.ContentDelivery. Here is an example:
var logger =
Tridion.ContentDelivery.Web.Utilities.LoggerFactory.GetLogger("Tridion.ContentDelivery.MyNamespace.MyClass");
Hope this helps.
P.S.: to answer your question: because you do not have an appender for it and the root logging is set to WARN. By default, the logback.xml contains appenders only for "com.tridion" but I guess that the output of this.getType().ToString() does not start with that string.

runscript executed multiple times for #Autowired jdbcTemplate and h2 in-memory database

I've inherited a project and am trying to get a set of integration tests running against an in-memory h2 database. In order for them to pass some tables, relationships and reference data needs creating.
I can see the problem in that the script referenced in RUNSCRIPT is being executed multiple times and therefore generating Index "XXX_IDX" already exists errors and other violations. So is there a way to force the script to only be run once or do I need a external database? It seems that the script is run on every connection which I assume is by design.
properties file
my.datasource.url=jdbc:h2:mem:my_db;DB_CLOSE_DELAY=-1;MODE=Oracle;MVCC=TRUE;INIT=RUNSCRIPT FROM 'classpath:/create-tables-and-ref-data.sql'
XML config
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="url" value="${my.datasource.url}"/>
<!-- other properties for username, password etc... -->
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="myDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
many Java classes in the following pattern
#Component
public class SomethingDAOImpl implements SomethingDAO {
#Autowired
public SomethingDAOImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
#Component
public class SomethingElseDAOImpl implements SomethingElseDAO {
#Autowired
public SomethingElseDAOImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
With the default bean scope being singleton I thought this would just work, but I guess i'm missing something. Also, if I switch to an real Oracle instance that already has the tables and reference data set-up, the tests all pass.
In many cases, it is possible to write the SQL script so that no exceptions are thrown:
create table if not exists test(id int, name varchar(255));
create index if not exists test_idx on test(name);
I ended up using an alternative approach, as I could not write the SQL in a way that could be reapplied without error.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd">
<jdbc:initialize-database data-source="myDataSource" enabled="true" ignore-failures="ALL">
<jdbc:script location="classpath:create-and-alter-tables-first-then-add-test-data.sql" />
</jdbc:initialize-database>
</beans>
which is executed once at context initialization.
Note: other namespaces and beans omitted for brevity.

Resources