I have an update query:
#Modifying
#Transactional
#Query("UPDATE Admin SET firstname = :firstname, lastname = :lastname, login = :login, superAdmin = :superAdmin, preferenceAdmin = :preferenceAdmin, address = :address, zipCode = :zipCode, city = :city, country = :country, email = :email, profile = :profile, postLoginUrl = :postLoginUrl WHERE id = :id")
public void update(#Param("firstname") String firstname, #Param("lastname") String lastname, #Param("login") String login, #Param("superAdmin") boolean superAdmin, #Param("preferenceAdmin") boolean preferenceAdmin, #Param("address") String address, #Param("zipCode") String zipCode, #Param("city") String city, #Param("country") String country, #Param("email") String email, #Param("profile") String profile, #Param("postLoginUrl") String postLoginUrl, #Param("id") Long id);
I'm trying to use it in an integration test:
adminRepository.update("Toto", "LeHeros", admin0.getLogin(), admin0.getSuperAdmin(), admin0.getPreferenceAdmin(), admin0.getAddress(), admin0.getZipCode(), admin0.getCity(), admin0.getCountry(), admin0.getEmail(), admin0.getProfile(), admin0.getPostLoginUrl(), admin0.getId());
Admin loadedAdmin = adminRepository.findOne(admin0.getId());
assertEquals("Toto", loadedAdmin.getFirstname());
assertEquals("LeHeros", loadedAdmin.getLastname());
But the fields are not updated and retain their initial values, the test thus failing.
I tried adding a flush right before the findOne query:
adminRepository.flush();
But the failed assertion remained identical.
I can see the update sql statement in the log:
update admin set firstname='Toto', lastname='LeHeros', login='stephane', super_admin=0, preference_admin=0,
address=NULL, zip_code=NULL, city=NULL, country=NULL, email='stephane#thalasoft.com', profile=NULL,
post_login_url=NULL where id=2839
But the log shows no sql that could relate to the finder:
Admin loadedAdmin = adminRepository.findOne(admin0.getId());
The finder sql statement is not making its way to the database.
Is it ignored for some caching reason ?
If I then add a call to the findByEmail and findByLogin finders as in:
adminRepository.update("Toto", "LeHeros", "qwerty", admin0.getSuperAdmin(), admin0.getPreferenceAdmin(), admin0.getAddress(), admin0.getZipCode(), admin0.getCity(), admin0.getCountry(), admin0.getEmail(), admin0.getProfile(), admin0.getPostLoginUrl(), admin0.getId());
Admin loadedAdmin = adminRepository.findOne(admin0.getId());
Admin myadmin = adminRepository.findByEmail(admin0.getEmail());
Admin anadmin = adminRepository.findByLogin("qwerty");
assertEquals("Toto", anadmin.getFirstname());
assertEquals("Toto", myadmin.getFirstname());
assertEquals("Toto", loadedAdmin.getFirstname());
assertEquals("LeHeros", loadedAdmin.getLastname());
then I can see in the log the sql statement being generated:
But the assertion:
assertEquals("Toto", myadmin.getFirstname());
still fails even though the trace shows the same domain object was retrieved:
TRACE [BasicExtractor] found [1037] as column [id14_]
One other thing that puzzles me with this other finder is that it shows a limit 2 clause even though it is supposed to return only one Admin object.
I thought there would always be a limit 1 when returning one domain object. Is this a wrong assumption on Spring Data ?
When pasting in a MySQL client, the sql statements displayed in the console log, the logic works fine:
mysql> insert into admin (version, address, city, country, email, firstname, lastname, login, password,
-> password_salt, post_login_url, preference_admin, profile, super_admin, zip_code) values (0,
-> NULL, NULL, NULL, 'zemail#thalasoft.com039', 'zfirstname039', 'zlastname039', 'zlogin039',
-> 'zpassword039', '', NULL, 0, NULL, 1, NULL);
Query OK, 1 row affected (0.07 sec)
mysql> select * from admin;
+------+---------+---------------+--------------+-----------+--------------+---------------+-------------+------------------+---------+----------+------+---------+-------------------------+---------+----------------+
| id | version | firstname | lastname | login | password | password_salt | super_admin | preference_admin | address | zip_code | city | country | email | profile | post_login_url |
+------+---------+---------------+--------------+-----------+--------------+---------------+-------------+------------------+---------+----------+------+---------+-------------------------+---------+----------------+
| 1807 | 0 | zfirstname039 | zlastname039 | zlogin039 | zpassword039 | | 1 | 0 | NULL | NULL | NULL | NULL | zemail#thalasoft.com039 | NULL | NULL |
+------+---------+---------------+--------------+-----------+--------------+---------------+-------------+------------------+---------+----------+------+---------+-------------------------+---------+----------------+
1 row in set (0.00 sec)
mysql> update admin set firstname='Toto', lastname='LeHeros', login='qwerty', super_admin=0, preference_admin=0, address=NULL, zip_code=NULL, city=NULL, country=NULL, email='stephane#thalasoft.com', profile=NULL, post_login_url=NULL where id=1807;
Query OK, 1 row affected (0.07 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from admin; +------+---------+-----------+----------+--------+--------------+---------------+-------------+------------------+---------+----------+------+---------+------------------------+---------+----------------+
| id | version | firstname | lastname | login | password | password_salt | super_admin | preference_admin | address | zip_code | city | country | email | profile | post_login_url |
+------+---------+-----------+----------+--------+--------------+---------------+-------------+------------------+---------+----------+------+---------+------------------------+---------+----------------+
| 1807 | 0 | Toto | LeHeros | qwerty | zpassword039 | | 0 | 0 | NULL | NULL | NULL | NULL | stephane#thalasoft.com | NULL | NULL |
+------+---------+-----------+----------+--------+--------------+---------------+-------------+------------------+---------+----------+------+---------+------------------------+---------+----------------+
1 row in set (0.00 sec)
mysql> select admin0_.id as id14_, admin0_.version as version14_, admin0_.address as address14_, admin0_.city as city14_, admin0_.country as country14_, admin0_.email as email14_, admin0_.firstname as firstname14_, admin0_.lastname as lastname14_, admin0_.login as login14_, admin0_.password as password14_, admin0_.password_salt as password11_14_, admin0_.post_login_url as post12_14_, admin0_.preference_admin as preference13_14_, admin0_.profile as profile14_, admin0_.super_admin as super15_14_, admin0_.zip_code as zip16_14_ from admin admin0_ where admin0_.email='stephane#thalasoft.com' limit 2;
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
| id14_ | version14_ | address14_ | city14_ | country14_ | email14_ | firstname14_ | lastname14_ | login14_ | password14_ | password11_14_ | post12_14_ | preference13_14_ | profile14_ | super15_14_ | zip16_14_ |
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
| 1807 | 0 | NULL | NULL | NULL | stephane#thalasoft.com | Toto | LeHeros | qwerty | zpassword039 | | NULL | 0 | NULL | 0 | NULL |
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
1 row in set (0.00 sec)
mysql> select admin0_.id as id14_, admin0_.version as version14_, admin0_.address as address14_, admin0_.city as city14_, admin0_.country as country14_, admin0_.email as email14_, admin0_.firstname as firstname14_, admin0_.lastname as lastname14_, admin0_.login as login14_, admin0_.password as password14_, admin0_.password_salt as password11_14_, admin0_.post_login_url as post12_14_, admin0_.preference_admin as preference13_14_, admin0_.profile as profile14_, admin0_.super_admin as super15_14_, admin0_.zip_code as zip16_14_ from admin admin0_ where admin0_.login='qwerty' limit 2;
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
| id14_ | version14_ | address14_ | city14_ | country14_ | email14_ | firstname14_ | lastname14_ | login14_ | password14_ | password11_14_ | post12_14_ | preference13_14_ | profile14_ | super15_14_ | zip16_14_ |
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
| 1807 | 0 | NULL | NULL | NULL | stephane#thalasoft.com | Toto | LeHeros | qwerty | zpassword039 | | NULL | 0 | NULL | 0 | NULL |
+-------+------------+------------+---------+------------+------------------------+--------------+-------------+----------+--------------+----------------+------------+------------------+------------+-------------+-----------+
1 row in set (0.00 sec)
So why is this not reflected at the Java level ?
The EntityManager doesn't flush change automatically by default. You should use the following option with your statement of query:
#Modifying(clearAutomatically = true)
#Query("update RssFeedEntry feedEntry set feedEntry.read =:isRead where feedEntry.id =:entryId")
void markEntryAsRead(#Param("entryId") Long rssFeedEntryId, #Param("isRead") boolean isRead);
I finally understood what was going on.
When creating an integration test on a statement saving an object, it is recommended to flush the entity manager so as to avoid any false negative, that is, to avoid a test running fine but whose operation would fail when run in production. Indeed, the test may run fine simply because the first level cache is not flushed and no writing hits the database. To avoid this false negative integration test use an explicit flush in the test body. Note that the production code should never need to use any explicit flush as it is the role of the ORM to decide when to flush.
When creating an integration test on an update statement, it may be necessary to clear the entity manager so as to reload the first level cache. Indeed, an update statement completely bypasses the first level cache and writes directly to the database. The first level cache is then out of sync and reflects the old value of the updated object. To avoid this stale state of the object, use an explicit clear in the test body. Note that the production code should never need to use any explicit clear as it is the role of the ORM to decide when to clear.
My test now works just fine.
I was able to get this to work. I will describe my application and the integration test here.
The Example Application
The example application has two classes and one interface that are relevant to this problem:
The application context configuration class
The entity class
The repository interface
These classes and the repository interface are described in the following.
The source code of the PersistenceContext class looks as follows:
import com.jolbox.bonecp.BoneCPDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.Properties;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "net.petrikainulainen.spring.datajpa.todo.repository")
#PropertySource("classpath:application.properties")
public class PersistenceContext {
protected static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
protected static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
protected static final String PROPERTY_NAME_DATABASE_URL = "db.url";
protected static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";
private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
private static final String PROPERTY_NAME_HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
private static final String PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY = "hibernate.ejb.naming_strategy";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
private static final String PROPERTY_PACKAGES_TO_SCAN = "net.petrikainulainen.spring.datajpa.todo.model";
#Autowired
private Environment environment;
#Bean
public DataSource dataSource() {
BoneCPDataSource dataSource = new BoneCPDataSource();
dataSource.setDriverClass(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
dataSource.setJdbcUrl(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
dataSource.setUsername(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_USERNAME));
dataSource.setPassword(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
return dataSource;
}
#Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setPackagesToScan(PROPERTY_PACKAGES_TO_SCAN);
Properties jpaProperties = new Properties();
jpaProperties.put(PROPERTY_NAME_HIBERNATE_DIALECT, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
jpaProperties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
jpaProperties.put(PROPERTY_NAME_HIBERNATE_HBM2DDL_AUTO, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_HBM2DDL_AUTO));
jpaProperties.put(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_NAMING_STRATEGY));
jpaProperties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
}
Let's assume that we have a simple entity called Todo which source code looks as follows:
#Entity
#Table(name="todos")
public class Todo {
public static final int MAX_LENGTH_DESCRIPTION = 500;
public static final int MAX_LENGTH_TITLE = 100;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "description", nullable = true, length = MAX_LENGTH_DESCRIPTION)
private String description;
#Column(name = "title", nullable = false, length = MAX_LENGTH_TITLE)
private String title;
#Version
private long version;
}
Our repository interface has a single method called updateTitle() which updates the title of a todo entry. The source code of the TodoRepository interface looks as follows:
import net.petrikainulainen.spring.datajpa.todo.model.Todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface TodoRepository extends JpaRepository<Todo, Long> {
#Modifying
#Query("Update Todo t SET t.title=:title WHERE t.id=:id")
public void updateTitle(#Param("id") Long id, #Param("title") String title);
}
The updateTitle() method is not annotated with the #Transactional annotation because I think that it is best to use a service layer as a transaction boundary.
The Integration Test
The Integration Test uses DbUnit, Spring Test and Spring-Test-DBUnit. It has three components which are relevant to this problem:
The DbUnit dataset which is used to initialize the database into a known state before the test is executed.
The DbUnit dataset which is used to verify that the title of the entity is updated.
The integration test.
These components are described with more details in the following.
The name of the DbUnit dataset file which is used to initialize the database to known state is toDoData.xml and its content looks as follows:
<dataset>
<todos id="1" description="Lorem ipsum" title="Foo" version="0"/>
<todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
</dataset>
The name of the DbUnit dataset which is used to verify that the title of the todo entry is updated is called toDoData-update.xml and its content looks as follows (for some reason the version of the todo entry was not updated but the title was. Any ideas why?):
<dataset>
<todos id="1" description="Lorem ipsum" title="FooBar" version="0"/>
<todos id="2" description="Lorem ipsum" title="Bar" version="0"/>
</dataset>
The source code of the actual integration test looks as follows (Remember to annotate the test method with the #Transactional annotation):
import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.TransactionDbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;
import com.github.springtestdbunit.annotation.ExpectedDatabase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import org.springframework.test.context.support.DirtiesContextTestExecutionListener;
import org.springframework.test.context.transaction.TransactionalTestExecutionListener;
import org.springframework.transaction.annotation.Transactional;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {PersistenceContext.class})
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class })
#DatabaseSetup("todoData.xml")
public class ITTodoRepositoryTest {
#Autowired
private TodoRepository repository;
#Test
#Transactional
#ExpectedDatabase("toDoData-update.xml")
public void updateTitle_ShouldUpdateTitle() {
repository.updateTitle(1L, "FooBar");
}
}
After I run the integration test, the test passes and the title of the todo entry is updated. The only problem which I am having is that the version field is not updated. Any ideas why?
I undestand that this description is a bit vague. If you want to get more information about writing integration tests for Spring Data JPA repositories, you can read my blog post about it.
The underlying problem here is the 1st level cache of JPA.
From the JPA spec Version 2.2 section 3.1. emphasise is mine:
An EntityManager instance is associated with a persistence context. A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance.
This is important because JPA tracks changes to that entity in order to flush them to the database.
As a side effect it also means within a single persistence context an entity gets only loaded once.
This why reloading the changed entity doesn't have any effect.
You have a couple of options how to handle this:
Evict the entity from the EntityManager.
This may be done by calling EntityManager.detach, annotating the updating method with #Modifying(clearAutomatically = true) which evicts all entities.
Make sure changes to these entities get flushed first or you might end up loosing changes.
Use EntityManager.refresh().
Use a different persistence context to load the entity.
The easiest way to do this is to do it in a separate transaction.
With Spring this can be done by having separate methods annotated with #Transactional on beans called from a bean not annotated with #Transactional.
Another way is to use a TransactionTemplate which works especially nicely in tests where it makes transaction boundaries very visible.
I struggled with the same problem where I was trying to execute an update query like the same as you did-
#Modifying
#Transactional
#Query(value = "UPDATE SAMPLE_TABLE st SET st.status=:flag WHERE se.referenceNo in :ids")
public int updateStatus(#Param("flag")String flag, #Param("ids")List<String> references);
This will work if you have put #EnableTransactionManagement annotation on the main class.
Spring 3.1 introduces the #EnableTransactionManagement annotation to be used in on #Configuration classes and enable transactional support.
I want to get the names of all the public methods (void return type and no arguments) of a class1 which is dependent on some other class2.
I am loading class through UrlClassLoader. Now when i am calling getDeclaredMethods, it is throwing NoClassDefFoundError caused by ClassNotFoundException.
I am having 3 mvn modules as
SampleClassLoader: Using it to get the methods of class of Module1.
Module1: Its class using the reference to classes of Module2. And has a dependency of Module2 in its pom.xml also.
Module2
The whole module structure looks like:
Project Structure
ClassLoadingTest
|----- Module1
| |--- pom.xml
| |--- src/main/java/
| | |--- com.classloadingtest.module1
| | |
| | |--- Module1Class1.java
| | |--- Module1Class2.java
|
|----- Module2
| |--- pom.xml
| |--- src/main/java/
| | |--- com.classloadingtest.module2
| | |
| | |--- Module2Class.java
|
|----- SampleClassLoader
| |--- pom.xml
| |--- src/main/java/
| | |--- com.classloadingtest.sampleClassLoader
| | |
| | |--- SampleClassLoader.java
Module1Class1.java
public class Module1Class1 {
public void claas1Fun() {
Module2Class module2ClassObj = new Module2Class();
module2ClassObj.module2Fun();
}
}
Module1Class2.java
public class Module1Class2 {
public void class2Fun(){
try {
Module2Class module2ClassObj = new Module2Class();
module2ClassObj.module2Fun();
} catch(Exception e ){
}
}
}
Module2Class.java
public class Module2Class {
public void module2Fun(){
}
}
SampleClassLoader.java
public class SampleClassLoader {
public static void main(String[] args) {
try {
URL mainSourceClassPathURL = new URL("file:" + System.getProperty("user.dir") + "/ClassLoadingTest/Module1/target/classes/");
URL[] urls = { mainSourceClassPathURL};
ClassLoader classLoader = URLClassLoader.newInstance(urls);
Class<?> testCaseClass = classLoader.loadClass("com.classloadingtest.module1.Module1Class1");
Method method[] = testCaseClass.getDeclaredMethods();
for (int i = 0 ; i < method.length ; i++) {
System.out.println(method[i].getName());
}
} catch (Exception e){
e.printStackTrace();
}
}
}
Now, When Running the SampleClassLoader for class Module1Class1 prints
claas1Fun
But when running for class Module1Class2 it is giving NoClassDefFoundError as:
Exception in thread "main" java.lang.NoClassDefFoundError: com/classloadingtest/module2/Module2Class
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
at java.lang.Class.getDeclaredMethods(Class.java:1975)
at com.classloadingtest.sampleClassLoader.SampleClassLoader.main(SampleClassLoader.java:26)
Caused by: java.lang.ClassNotFoundException: com.classloadingtest.module2.Module2Class
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.net.FactoryURLClassLoader.loadClass(URLClassLoader.java:814)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 4 more
I am having two questions here that is:
When using try catch, why it is giving error?
If the class1 is already loaded at classLoader.loadClass then why getDeclaredMethods method need to load dependent classes?
Concerning the try-catch issue, the point is that java.lang.NoClassDefFoundError is not an Exception, is an Error which is a more severe kind of Throwable.
Errors are, generally speaking, unrecoverable (Like OutOfMemoryError or StackOverflowError, ...), so they are seldom catched.
If you want to catch NoClassDefFoundError you should add catch(NoClassDefFoundError e) to your try
I’m learning Java and Hibernate. Right now, I’m having trouble understanding how to use a custom physical naming strategy: While the PhysicalNamingStrategy object is indeed instantiated, the toPhysicalTableName or toPhysicalColumnName methods are never called – not that I can see with a debugger, at least.
Versions: Java 1.8, Hibernate 5.2.10.Final, on macOS 10.12.
Here’s a minimal project:
#Entity
public class Cake {
#Id
private long id;
private String name;
private String FLAVOUR;
private int sErViNg;
public Cake(String name, String flavour, int serving) {
this.name = name;
this.FLAVOUR = flavour;
this.sErViNg = serving;
}
// getters and setters
public class Main {
public static void main (String[] args) {
Transaction tx = null;
try (
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
) {
tx = session.beginTransaction();
Cake cake = new Cake("Molten Chocolate Cake", "chocolate", 1);
session.save(cake);
tx.commit();
}
catch (Exception e) {
e.printStackTrace();
if ( tx != null ) {
tx.rollback();
}
}
}
}
public class AllCapsPhysicalNamingStrategy
extends PhysicalNamingStrategyStandardImpl implements Serializable {
public static final AllCapsPhysicalNamingStrategy INSTANCE
= new AllCapsPhysicalNamingStrategy();
#Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
return new Identifier(name.getText().toUpperCase(), name.isQuoted());
}
#Override
public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment context) {
return new Identifier(name.getText().toUpperCase(), name.isQuoted());
}
}
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/cake</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.hbm2ddl.auto">create</property>
<property name="hibernate.physical_naming_strategy">com.example.AllCapsPhysicalNamingStrategy</property>
<mapping class="com.example.Cake"/>
</session-factory>
</hibernate-configuration>
Here’s the table I get:
[cake]> SELECT * FROM cake;
+----+-----------+-----------------------+---------+
| id | FLAVOUR | name | sErViNg |
+----+-----------+-----------------------+---------+
| 0 | chocolate | Molten Chocolate Cake | 1 |
+----+-----------+-----------------------+---------+
I would expect:
+----+-----------+-----------------------+---------+
| ID | FLAVOUR | NAME | SERVING |
+----+-----------+-----------------------+---------+
| 0 | chocolate | Molten Chocolate Cake | 1 |
+----+-----------+-----------------------+---------+
What am I doing wrong here?
This isn't very well documented but unfortunately it seems Hibernate doesn't support that particular property being set in hibernate.cfg.xml. To quote from a very old Hibernate forum post:
You can set the properties given Environment.java class only in
hibernate.properties or hibernate.cfg.xml. Rest of the properties like
NamingStrategy has to be configured with Configuration class.
So would recommend removing the property and instead setting this in code on the Configuration instance, as proposed by Shiv Raghuwanshi.
You can set in configuration also.
public class Main {
public static void main (String[] args) {
Transaction tx = null;
try (
Configuration configuration =new Configuration();
configuration.setPhysicalNamingStrategy(new AllCapsPhysicalNamingStrategy());
SessionFactory sessionFactory = configuration.configure().buildSessionFactory();
Session session = sessionFactory.openSession();
) {
tx = session.beginTransaction();
Cake cake = new Cake("Molten Chocolate Cake", "chocolate", 1);
session.save(cake);
tx.commit();
}
catch (Exception e) {
e.printStackTrace();
if ( tx != null ) {
tx.rollback();
}
}
}
}
There is nothing wrong with the your configuration. It is just that bootstrapping hibernate using Configuration object requires you to set some of the config property on configuration object itself. These configuration specified via properties will get ignored.
Also, bootstrapping hibernate using Configuration object is considered as "legacy" way (as per official hibernate docs) and newer way is recommended of bootstrapping the hibernate as shown below.
public static void main(String[] args) {
Transaction tx = null;
StandardServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
.configure() // using "hibernate.cfg.xml"
.build();
Metadata metadata = new MetadataSources(standardRegistry).buildMetadata();
try (
SessionFactory sessionFactory = metadata.getSessionFactoryBuilder().build();
Session session = sessionFactory.openSession();) {
tx = session.beginTransaction();
Cake cake = new Cake("Molten Chocolate Cake", "chocolate", 1);
session.save(cake);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
if (tx != null) {
tx.rollback();
}
}
}
This will pick the Physical naming strategy specified as hibernate property in hibernate.cfg.xml file.
I am trying to use JPA for persistence in a Java Web Application for a RESTful WebService. The program runs through the EntityManager, persisting an Entity class, being surrounded bz a transaction (begin and commit). The transaction makes troubles though. When I check the MySQL database the necessary tables are created, but no entries inside of them. How can that be? What's wrong in the code?
My service class:
#Stateless
public class ReportService {
#PersistenceContext(unitName = "AbcPU") // default type is PersistenceContextType.TRANSACTION
EntityManager em;
public void saveDog() {
System.out.println("BBBBB Start ReportService.saveDog();");
Doggy doggy = new Doggy();
doggy.setDogName("Wuffi");
try {
System.out.println("BBBBB Persist Success ReportService.saveDog();");
em.getTransaction().begin();
em.persist(doggy);
em.flush();
em.getTransaction().commit();
} catch (Exception ex) {
System.out.println("BBBBB Persist Fail ReportService.saveDog();");
System.err.println("Error with em.persist(doggy): " + ex.getMessage());
}
System.out.println("BBBBB Stop ReportService.saveDog();");
}
}
My resource class:
#Path("report")
#Produces(MediaType.APPLICATION_JSON)
public class ReportResource {
#EJB
private ReportService rs;
#GET
public Response findReports() {
final List<Report> reports = rs.findAllReports();
System.out.println("AAAAA Start rs.saveDog();");
rs.saveDog();
System.out.println("AAAAA Stop rs.saveDog();");
return Response.ok(new GenericEntity<List<Report>>(reports) {})
.build();
}
}
My entity class:
#Entity
public class Doggy implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private String dogName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDogName() {
return dogName;
}
public void setDogName(String dogName) {
this.dogName = dogName;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Doggy)) {
return false;
}
Doggy other = (Doggy) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "com.glasses.pandora.domain.Doggy[ id=" + id + " ]";
}
}
My persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="AbcPU" transaction-type="JTA">
<jta-data-source>java:/jdbc/abc</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
</properties>
</persistence-unit>
</persistence>
My Maven dependencies in the pom.xml:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.1.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>unknown.binary</groupId>
<artifactId>hibernate-jpamodelgen-4.3.1.Final</artifactId>
<version>SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
My console output after having run:
01:58:54,158 INFO [stdout] (default task-23) AAAAA Start rs.saveDog();
01:58:54,160 INFO [stdout] (default task-23) BBBBB Start ReportService.saveDog();
01:58:54,160 INFO [stdout] (default task-23) BBBBB Persist Success ReportService.saveDog();
01:58:54,160 INFO [stdout] (default task-23) BBBBB Persist Fail ReportService.saveDog();
01:58:54,161 ERROR [stderr] (default task-23) Error with em.persist(doggy): A JTA EntityManager cannot use getTransaction()
01:58:54,161 INFO [stdout] (default task-23) BBBBB Stop ReportService.saveDog();
01:58:54,162 INFO [stdout] (default task-23) AAAAA Stop rs.saveDog();
The mysql show tables:
mysql> show tables;
+--------------------+
| Tables_in_abc |
+--------------------+
| Doggy |
| hibernate_sequence |
+--------------------+
2 rows in set (0.00 sec)
mysql> SELECT * FROM Doggy;
Empty set (0.00 sec)
mysql>
There are a few problems. The first, mentioned by hwellmann, is that you have not specified a datasource. JTA requires a so that connections associated to the JTA transaction are used.
The second is that you don't seem to have wrapped the saveDog method in a transaction. The persist call will only register the Entity with the context; it only gets inserted in the database when the associated transaction is committed, or EntityManager.flush() is called. If you call em.flush in your save method, this will validate that your EntityManager is correctly associated to a transaction and that the insert is successful.
Check the server logs to see if your application is indeed using a MySQL datasource or the embedded default H2 datasource.
Your persistence.xml does not contain a <jta-data-source>, so I'd say the default datasource is being used.
See the Wiki article about Datasource Configuration.
Look at the error - Error with em.persist(doggy): A JTA EntityManager cannot use getTransaction(). EJB is handling transactions automatically - remove the following from your ReportService em.getTransaction().begin() , em.flush(); and em.getTransaction().commit();
Your method should be like this:
public void saveDog() {
System.out.println("BBBBB Start ReportService.saveDog();");
Doggy doggy = new Doggy();
doggy.setDogName("Wuffi");
try {
System.out.println("BBBBB Persist Success ReportService.saveDog();");
em.persist(doggy);
} catch (Exception ex) {
System.out.println("BBBBB Persist Fail ReportService.saveDog();");
System.err.println("Error with em.persist(doggy): " + ex.getMessage());
}
System.out.println("BBBBB Stop ReportService.saveDog();");
}
I am pretty sure you need to look up your persistence context and assign the entitymanager from that.
#Dependent
#Stateful
public class ReportService {
#PersistenceContext(name = "persistence/AbcPU", unitName = "AbcPU")
public abstract EntityManager getEntityManager() {
EntityManager em = null;
try {
Context envCtx = InitialContext.doLookup("java:comp/env");
em = (EntityManager) envCtx.lookup("persistence/AbcPU");
} catch (NamingException ne) {
//Handle Exception
}
return em;
}
public void saveDog() {
System.out.println("BBBBB Start ReportService.saveDog();");
Doggy doggy = new Doggy();
doggy.setDogName("Wuffi");
try {
System.out.println("BBBBB Persist Success ReportService.saveDog();");
getEntityManager().persist(doggy);
} catch (Exception ex) {
System.out.println("BBBBB Persist Fail ReportService.saveDog();");
System.err.println("Error with em.persist(doggy): " + ex.getMessage());
}
System.out.println("BBBBB Stop ReportService.saveDog();");
}
}