#Transactional annotation is not working for me in a springboot-hibernate project. I am using the annotation configuration for which i have done the following configuration. I have tried using the #Transactional on method and class name in the service layer and also in the dao layer, but no luck. I think there is some issue with the transaction manager configuration but I am unable to figure out how to configure the transaction manager in my application.
application.properties
#spring configuration
spring.jpa.show-sql = true
#spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
#spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
dao
#Autowired
private EntityManagerFactory entityManagerFactory;
#Override
public void deleteSMS(String id) {
logger.info("Delete sms details with id :: \"" + id + "\"");
Session session = null;
try {
session = entityManagerFactory.unwrap(SessionFactory.class).openSession();
SMSDetails smsDetails = session.get(SMSDetails.class, Long.parseLong(id));
if (smsDetails != null)
session.delete(smsDetails);
} catch (Exception e) {
logger.error("Error occured while deleting the sms with id :: \"" + id + "\" :: " + e.getMessage());
throw e;
} finally {
if (session != null)
session.close();
}
}
service
#Override
#Transactional
public void deleteSMS(String id) {
smsDao.deleteSMS(id);
}
I am using spring boot 2.1.3 and hibernate. I have configured the entitymanagerfactory as above and used the following to obtain session
session = entityManagerFactory.unwrap(SessionFactory.class).openSession();
but the #Transactional is not working
You are opening a Session inside a #Transactional method. This is wrong, because when you annotate method as transactional, it is invoked inside a single session, you don't need to open another one.
I had the same problem
I added this annotation on my class application
#EnableTransactionManagement (proxyTargetClass = true)
And this on the method on my class service
#Transactional (rollbackFor = Exception.class)
Related
I'm a C# developer and need to maintain an existing Java Service application developed using spring boot framework. The responsible developer left the company some time ago so I have no possibility to get some help...
So far I have no experience with Java and the used spring boot framework.
What I need to achive:
Check if used h2 Database is corrupted
If corrupted: Delete the database and create a new empty one
I guess I need to implement the check and recreation in the main entry point
public static void main(String[] args) {
SpringApplication.run(MessageServiceApplication.class, args);
}
As I know spring-boot and Hibernate creates the db automatically on startup if the database does not exists. So far so good. Now I need to check if the database is corrupted. I thought about executing a query on the database and if I get an exception I recreate the database.
The Database is a h2 file database.
Hopefully I can get some assistance.
Edit #1
I thought about implementing a utils class which gets called on startup:
public class H2DbUtils {
public boolean IsH2FileDatabaseCorrupted()
{
boolean isCorrupted = false;
// Implement Logic to determine if db is corrupted
return isCorrupted;
}
public boolean ReCreateH2DatabaseFile()
{
boolean reCreated = false;
// Implement Logic to recreate db
return reCreated;
}
}
Calling this class on startup
public static void main(String[] args) {
H2DbUtils h2DbUtils = new H2DbUtils();
if(h2DbUtils.IsH2FileDatabaseCorrupted()) {
h2DbUtils.ReCreateH2DatabaseFile();
}
SpringApplication.run(MessageServiceApplication.class, args);
}
Update 2018-03-20
Currently found the following solution to achive this:
#Configuration
#Component
public class DataSourceBean {
#Autowired
private Environment currentEnvironment;
private final Logger logInstance = LoggerFactory.getLogger(this.getClass());
#Bean
#Primary
public DataSource dataSource()
{
DataSource dataSource = null;
try
{
// We try to get the Meta Data out of the database.
// If this fails the database is corrupted or has an other problem
// All in all this means we need to delete the current database file
// to avoid further problems.
dataSource = this.getDataSource();
dataSource.getConnection().getMetaData();
return dataSource;
}
catch (Exception ex)
{
logInstance.error("The h2 database file '{}' seems to be corrupted! Error: {}",
currentEnvironment.getProperty("dataBaseFile"),
ex.getMessage());
// dataBaseFile=./db/mydatabase.db
String databaseFilePath = String.format("%s.%s", currentEnvironment.getProperty("dataBaseFile"), "h2.db");
databaseFilePath = databaseFilePath.replace("/", "\\");
File databaseFile = new File(databaseFilePath);
if (databaseFile.exists()) {
File parentDirectory = new File(databaseFile.getParent());
if (parentDirectory.isDirectory()) {
try {
FileUtils.deleteDirectory(parentDirectory);
} catch (Exception fex) {
logInstance.error("Error occurred deleting the folder {}. Error: {}",
parentDirectory.getAbsolutePath(),
fex.getMessage());
}
}
}
dataSource = this.getDataSource();
}
finally {
return dataSource;
}
}
#ConfigurationProperties(prefix = "spring.datasource")
private DataSource getDataSource() {
return DataSourceBuilder.create()
.url(currentEnvironment.getProperty("spring.datasource.url"))
.driverClassName(currentEnvironment.getProperty("spring.datasource.driverClassName"))
.username(currentEnvironment.getProperty("spring.datasource.username"))
.password(currentEnvironment.getProperty("spring.datasource.password"))
.build();
}
It is possible to overwrite the DataSource bean and check database files
#Bean
#Primary // this will override the datasource autoconfiguration and use your own everywhere
public DataSource dataSource() {
// Open Connection
// Check Database
// Close Connection
// IF File corrupted delete files
// create regular data source
}
I've tried to add several listeners to the spring boot application, for example:
SpringApplication springApplication = new SpringApplication(testApplication.class);
springApplication.addListeners(new FailedEvent(testApplication.class));
SpringApplication.run(testApplication.class, args);
But I never get to one of this listeners in the startup of the spring application. As SpringApplication.run seems to initzialise the whole spring context it is also not possible to inject or get the configuration environment to get the connection string as the application stops within SpringApplication.run as the db is corrupted.
I assume that spring tries to initzialise hibernate and so on and fails to create a database connection as the db is corrupted
org.h2.jdbc.JdbcSQLException: Allgemeiner Fehler: "java.lang.RuntimeException: rowcount remaining=2 SYS"
General error: "java.lang.RuntimeException: rowcount remaining=2 SYS" [50000-196]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345) ~[h2-1.4.196.jar:1.4.196]
at org.h2.message.DbException.get(DbException.java:168) ~[h2-1.4.196.jar:1.4.196]
at org.h2.message.DbException.convert(DbException.java:295) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.Database.openDatabase(Database.java:307) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.Database.<init>(Database.java:270) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.Engine.openSession(Engine.java:64) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.Engine.openSession(Engine.java:176) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.Engine.createSessionAndValidate(Engine.java:154) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.Engine.createSession(Engine.java:137) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.Engine.createSession(Engine.java:27) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.SessionRemote.connectEmbeddedOrServer(SessionRemote.java:354) ~[h2-1.4.196.jar:1.4.196]
at org.h2.jdbc.JdbcConnection.<init>(JdbcConnection.java:116) ~[h2-1.4.196.jar:1.4.196]
at org.h2.jdbc.JdbcConnection.<init>(JdbcConnection.java:100) ~[h2-1.4.196.jar:1.4.196]
at org.h2.Driver.connect(Driver.java:69) ~[h2-1.4.196.jar:1.4.196]
This happens within the SpringApplicatio.run context. Before I found no chance to check if the db is corrupted and if so delete the database.
I am loading keystore in Spring WS via JPA from database.
#Bean(name = "keyStore")
#Primary
KeyStoreFactoryBean keyStore() throws Exception {
try {
KeyStore keyStore = keyService.keyStore();
KeyStoreFactoryBean keyStoreFactory = new KeyStoreFactoryBean();
keyStoreFactory.setPassword(keyStore.getPassword());
keyStoreFactory.setLocation(keyStore.asResource());
keyStoreFactory.afterPropertiesSet();
return keyStoreFactory;
} catch (Exception e) {
throw e;
}
}
This code works, but if I update keystore record in database, KeyStoreFactoryBean is not reloaded (obviously). I aware of scope anotation for beans but not sure what scope is best for this situation?
How to "reload" this bean everytime database record is changed?
In Spring Boot, does jdbcTemplate not close the connection automatically once after the it executes the query?
In this case, I am executing a query using jdbcTemplate(where it connects to teradata) but the session is not closing after the query is executed. How can I close the session?
This is my dao file -
#Component
public class DDLReviewDao {
#Autowired
#Qualifier("devbJdbc")
private JdbcTemplate jdbcTemplate;
public static final Logger logger = LogManager.getLogger(DDLReviewDao.class);
public List<DDLObject> getDDLReviewData(DDLQuery ddlQuery) {
String selectSql = MacroGenerator.generateMacro(ddlQuery);
List<DDLObject> ddlObject = jdbcTemplate.query(selectSql, new DDLMapper());
logger.info(ddlObject);
return ddlObject;
}
}
JdbcTemplate gets its connections from javax.sql.DataSource implementation - which is passed to its constructor link.
The DataSources can be basic (creates Connection object for each request) or pooling (has pool of connections and just 'borrows' one for given request's use).
So, it appears that the connection is not closing because you have passed some pooling datasource to JdbcTemplate named devbJdbc. If you realy want to close every connection opened to do the JdbcTemplate job, you can use a basic DataSource implementation: org.springframework.jdbc.datasource.SimpleDriverDataSource just like that:
#Configuration
class DevbConfig {
#Bean(name = "devbDataSource")
DataSource devbDataSource() {
try {
return new SimpleDriverDataSource(DriverManager.getDriver("jdbc:..."), "jdbc:...", "username", "password");
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
#Bean(name = "devbJdbc")
JdbcTemplate devbJdbc(#Qualifier("devbDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
In Spring Boot, does jdbcTemplate not close the connection
automatically once after the it executes the query?
Should it close the connection or return it to the connection pool (in case the DataSource is pooled)?
If you read the source code at http://grepcode.com/file/repo1.maven.org/maven2/org.springframework/spring-jdbc/4.1.7.RELEASE/org/springframework/jdbc/core/JdbcTemplate.java#JdbcTemplate.execute%28org.springframework.jdbc.core.StatementCallback%29it boils down to:
public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {
if (con == null) {
return;
}
if (dataSource != null) {
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && connectionEquals(conHolder, con)) {
// It's the transactional Connection: Don't close it.
conHolder.released();
return;
}
}
logger.debug("Returning JDBC Connection to DataSource");
doCloseConnection(con, dataSource);
}
and
public static void doCloseConnection(Connection con, DataSource dataSource) throws SQLException {
if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {
con.close();
}
}
Most-likely, if the DataSource instance is pooled, connections are release back for reuse and not closed.
According to the spring boot docs you can allocate a maximum number of connections to a spring pool like so:
spring.datasource.tomcat.max-active=50
This will obviously only work with the embedded webserver. If you are deploying it to something like a Jboss you'll have to configure that property in the Server config file.
I am using:
Web App (a filter opens session. DAO uses getCurrentSession())
Hibernate
Spring (AOP configuration over Service)
xml configuration for all
DTO between Mbean and Service
Well, I have two methods (business service):
service.findUser(..DTO..)
service.updateUser(..DTO..)
update throws org.hibernate.NonUniqueObjectException exception.
How can I avoid that?
I need to use update, not merge.
Thanks in advance.
//MBean.java method
public void testUpdateUser(ActionEvent e) {
System.out.println(name);
ServiceResponse<UserDto> serviceResponse = super.getPrincipalService().findUser(name);
UserDto userDto = serviceResponse.getResponseList().get(0);
//update some properties here
serviceResponse = super.getPrincipalService().updateUser(userDto);
LOG.info("" + serviceResponse);
}
//Service.java: update method
public ServiceResponse<UserDto> updateUser(UserDto userDto) {
LOG.info("");
ServiceResponse<UserDto> serviceResponse = new ServiceResponse<UserDto>();
try {
User user = this.getGlobalMapper().map(userDto, User.class);
//
this.getUserDao().update(user);
userDto = this.getGlobalMapper().map(user, UserDto.class);
serviceResponse.getResponseList().add(userDto);
serviceResponse.setOperationCodeResponse(ServiceResponseCode.OK);
serviceResponse.getMessages().add("Operacion OK");
} catch (Exception e) {
serviceResponse.getMessages().add(e.getMessage());
serviceResponse.setOperationCodeResponse(ServiceResponseCode.MODEL_ERROR);
LOG.error("", e);
}
return serviceResponse;
}
//Exception result
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.softlogia.copi.model.domain.User#155]
at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:696)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:296)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:241)
at org.hibernate.event.internal.DefaultUpdateEventListener.performSaveOrUpdate(DefaultUpdateEventListener.java:55)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:90)
at org.hibernate.internal.SessionImpl.fireUpdate(SessionImpl.java:705)
at org.hibernate.internal.SessionImpl.update(SessionImpl.java:697)
at org.hibernate.internal.SessionImpl.update(SessionImpl.java:693)
I am assuming you are using pure Hibernate as ORM; simply put, regardless of the status of your db, you have in your current Hibernate session different copies of the same row. To resolve this you can:
1) flush() the hibernate session after every writing operation on db (insert or update)
OR
2) In your update metod call merge() instead of saveOrUpdate()
I'm making some tests with Hazelcast XA Transactions and I'm having trouble when using Bitronix as the Transaction Manager.
I have followed Hazelcast official documentation:
http://docs.hazelcast.org/docs/3.5/manual/html/xatransactions.html
Libs versions:
Hazelcast: 3.5.4
Bitronix Transaction Manager: 2.1.4
Java: 1.7
Code:
Test method:
#Test
public void hazelcastBitronixXATransactionTest() throws Exception{
try{
doHazelcastXATransactionTest(createInstance("myCluster"));
}catch(Exception e){
Assert.fail();
System.out.println("Other Exception:" + e.getMessage());
}
}
Hazelcast instance creation method:
private HazelcastInstance createInstance(String clusterName){
System.setProperty("hazelcast.logging.type", "slf4j");
Config config = new Config();
config.getGroupConfig().setName(clusterName);
config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(true);
TcpIpConfig tcpIpConfig = config.getNetworkConfig().getJoin().getTcpIpConfig();
tcpIpConfig.setEnabled(false);
return Hazelcast.newHazelcastInstance(config);
}
Do method:
private void doHazelcastXATransactionTest(HazelcastInstance hazelcastInstance) throws Exception{
BitronixTransactionManager btm = TransactionManagerServices.getTransactionManager();
btm.setTransactionTimeout(60);
btm.begin();
HazelcastXAResource xaResource = hazelcastInstance.getXAResource();
Transaction transaction = btm.getTransaction();
transaction.enlistResource(xaResource);
try {
TransactionContext context = xaResource.getTransactionContext();
TransactionalMap map = context.getMap("m");
map.put("key", "value");
transaction.delistResource(xaResource, XAResource.TMSUCCESS);
btm.commit();
} catch (Exception e) {
System.out.println("Exception do rollback:" + e.getMessage());
btm.rollback();
}
}
I'm receiving a BitronixSystemException, thrown when trying to enlist the XA resource transaction.enlistResource(xaResource).
The complete stacktrace:
bitronix.tm.internal.BitronixSystemException: unknown XAResource HazelcastXaResource {myCluster}, it does not belong to a registered resource
Someone has faced this issue? Any clues on this?
Apparently Bitronix needs XAResources to be registered before using them.
I've added below line after obtaining the HazelcastXAResource and it worked.
EhCacheXAResourceProducer.registerXAResource(xaResource.getName(), xaResource);