Related
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 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 have a web application created with GWT that has a memory leak.
honestly, I can't produce this memory leak but when we deploy the application in the client environment with many users we face a memory leak problem.
I received this file maybe it can help me, it has the objects in the memory when the memory leak produced.
2'777'369'064 (62.72%) [32] 8 class */planning/canvas/shared/serializable/ActionCycleSZ 0x68759f768
|- 2'777'365'536 (62.72%) [256] 35 org/apache/catalina/loader/WebappClassLoader 0x688ce9df8
| |- 2'775'589'272 (62.68%) [48] 1 java/util/HashMap 0x688ceabe0
| | |- 2'775'589'224 (62.68%) [32'784] 3'533 array of java/util/HashMap$Entry 0x689af74c0
| | |- 2'763'509'944 (62.41%) [24] 2 java/util/HashMap$Entry 0x68a0b1f98
| | | |- 2'763'509'744 (62.41%) [40] 1 org/apache/catalina/loader/ResourceEntry 0x68a0b1fb0
| | | | |- 2'763'509'704 (62.41%) [32] 41 class
*/gwt/server/servlet/TaProjectsSessionManager 0x68653c8e8
| | | | |- 2'763'047'360 (62.4%) [32] 6 class */selfservice/SelfConfigurator 0x6875922a0
| | | | | |- 2'763'047'328 (62.4%) [16] 2 */gwt/server/servlet/TaProjectsSessionManager$1 0x689aee328
| | | | | | |- 2'154'573'968 (48.66%) [160] 30 */impl/HRSessionImpl 0x689ee49f8
| | | | | | | |- 2'138'350'824 (48.29%) [32] 3 java/util/Collections$SynchronizedMap 0x689ee4c70
| | | | | | | | |- 2'138'350'760 (48.29%) [64] 3 org/apache/commons/collections/map/LRUMap 0x689ee5218
| | | | | | | | | |- 2'134'913'368 (48.21%) [32] 2 org/apache/commons/collections/map/AbstractLinkedMap$LinkEntry 0x68a3573d0
| | | | | | | | | |- 3'437'328 (0.08%) [2'064] 121 array of org/apache/commons/collections/map/AbstractHashedMap$HashEntry 0x68a356bc8
| | | | | | | | | |- 16 (0%) [16] 1 org/apache/commons/collections/map/AbstractHashedMap$KeySet 0x69d443088
| | | | | | | | |- 32 (0%) [16] 2 java/util/Collections$SynchronizedSet 0x69d443098
| | | | | | | | |- 2'138'350'824 (48.29%) [32] 3 java/util/Collections$SynchronizedMap 0x689ee4c70
| | | | | | | |- 16'078'096 (0.36%) [104] 19 */impl/Dictionary 0x689ee4ad8
I conclude that the class ActionCycleSZ maybe produce the memory leak
this is ActionCycleSZ
public class ActionCycleSZ extends ActionDTO implements IsSerializable {
private CycleSZ bean;
public ActionCycleSZ() {
}
public ActionCycleSZ(Type actionType, CycleSZ bean ) {
super(actionType);
this.bean = bean;
}
public CycleSZ getBean(){
return bean;
}
public void setBean(CycleSZ bean){
this.bean = bean;
}
}
public class CycleSZ implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
String cycleLabel;
Date startDate;
Date endDate;
String startDateDTO;
String endDateDTO;
Integer numlign;
String accumulatedHours;
List<SiteSZ> listOfSites = new LinkedList<SiteSZ>();
//getter and setter
}
public class SiteSZ implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
int week;
String siteLabel;
Date startDate;
Date endDate;
String startHour;
String endHour;
String site;
String time;
String particularSlotTime;
Integer numlign;
DaySZ dayAttribute;
String accumulatedWeekHours;
Map<Util.WeekDays,DaySZ> mapAttributes = new LinkedHashMap<Util.WeekDays,DaySZ>();
boolean workedDay; //Flag for Exceptional Canevas Entry
boolean reposHebdo;
String contratId; //contratId for Exceptional Canevas Entry
ActionCycleSZ Contains the cycle and the action (create,delete,update)
this object will appear when the user create or update or delete this object
eventBus.addHandler(CycleSaveEvent.TYPE, new CycleSaveHandler() {
#Override
public void onCycleSaved(CycleSaveEvent event) {
final List<ActionCycleSZ> listCycleAction = new LinkedList<ActionCycleSZ>();
boolean newInsertion = getDetailsOfNewCycle(listCycleAction); //this methode can detect is there any new cycle and it will add actioncycleSZ
if (editedValuechange) {
getDeletedCycle(listCycleAction);
}
if (!newInsertion) getDetailsOfOldCycle(listCycleAction); getNotWorkedCycle(listOfNWSites,listCycleAction);
updateCycle(listCycleAction);
when the method updateCycle() called the listCycleAction will have all the cycles so the method will call an RPC service to save the cycles
This is updateCycle
public void updateCycle(final List<ActionCycleSZ> listCycleAction) {
//Call to service
new RpcCall2<String>() {
#Override
public void onFailure(Throwable caught) {
deletedListOfCycleSZ.clear();
view.setChantierGridUpdated(false);
}
#Override
public void onSuccess(final String message) {
isNewCycle=false;
SC.say(TAMessages.getMessage("ta.canvas.cycle.saved"), new BooleanCallback() {
#Override
public void execute(Boolean value) {
/* popup to make the user know that the cycle is saved */
}}}
#Override
protected void callService(AsyncCallback<AsyncCallback<String> callback) {
canvasServices.updateListActionCreatedCycle(listOfEmployees, listCycleAction, true, callback);
}
}.call();
I don't see anything that can make a memory leak so I install JProfiler to understand more my problem, I notice that the garbage collector for this object doesn't work whatever I call
canvasServices.updateListActionCreatedCycle(listOfEmployees,listCycleAction,true, callback);
even if the method is empty, On the other hand, the object will disappear in the memory if I don't give the object in the RPC call
what is the cause of the memory leak? am I on the right path?
I want to indicate that I am using GWT 2.5. can it be that GWT makes the memory leak ?
What to write within Visitor class?
We already made a grammar for our language. We don't need to perform any operations on it. If language is passed through written grammar, then we just want to take some of the objects from them.
given language as input:
Dec 17 14:00:00 103.56.229.11 firewall,info FFFW forward: in:<pppoe-mm.demo.649> out:sfp-sfpplus1.vlan113, proto TCP (ACK,PSH), 10.0.15.245:49831->103.235.46.39:443, NAT (10.0.15.245:49831->202.173.127.253:49831)->103.235.46.39:443, len 250
desired output:
Dec, 17, 14:00:00, 103.56.229.11, pppoe-mm.demo.649, TCP, 10.0.15.245:49831, 103.235.46.39:443, 202.173.127.253:49831
Our grammar (File name: sys.g): ( which is working well, We attested it using ANTLRWorks2 )
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
grammar sys;
r: IDENT NUM time ip x+ user xout proto xuser ipfull xtra ipfull xtra1 ipfull xtra ipfull xtra2 ipfull xtra3;
time: NUM SEP NUM SEP NUM;
ip: NUM USER NUM USER NUM USER NUM ;
ipfull: NUM USER NUM USER NUM USER NUM SEP NUM ;
x: (IDENT | SEP | NUM)+ LTHAN;
user: (IDENT | USER | NUM)+ ;
xuser: (IDENT | SEP | NUM)+ ;
xout: GTHAN IDENT+ SEP IDENT+ USER IDENT+ USER IDENT SEP IDENT;
proto: IDENT ;
xtra: USER GTHAN ;
xtra1: SEP IDENT SEP;
xtra2: SEP xtra;
xtra3: SEP IDENT NUM;
IDENT: ('a'..'z' | 'A'..'Z')('a'..'z' | 'A'..'Z' | '0'..'9')* ;
NUM: ('0'..'9')+ ;
LTHAN: '<' ;
GTHAN: '>' ;
SEP: ':' | ',' | '(' | ')' ;
USER: '-' | '.' ;
WS : (' ' | '\t' | '\r' | '\n')+ -> skip ;
Generated tree for the given language:
Generated tree for the language
Question 1:
We compiled our grammar file using antlr4.5 and we also used visitor. So our problem is how to print specific objects in another file?
Question 2:
Is it required to make another class named "value" which returns the value to the visitor?
EvalVisitor.java file:
public class EvalVisitor extends sysBaseVisitor{
//
}
Our main java file i.e. SysLogCheck.java, in which we are using Lexer (SysLexer.java) and Parser(SysParser.java) generated by our grammar sys.g file.
import org.antlr.v4.runtime.ANTLRFileStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import java.io.*;
import org.antlr.v4.runtime.*;
public class SysLogCheck {
public static void main(String[] args) throws Exception {
ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(new File("input.txt")));
sysLexer lexer = new sysLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
sysParser parser = new sysParser(tokens);
ParseTree tree = parser.r();
EvalVisitor visitor = new EvalVisitor();
visitor.visit(tree);
}
}
As for your first question:
Here is an example of a crude visitor, which outputs Dec, 17, 14:00:00:
In the line which reads /* do something with the results */ you can place some code which saves the results.
import org.antlr.v4.runtime.tree.ParseTree;
public class EvalVisitor extends sysBaseVisitor{
class LogEntry {
String ident1;
String dayNum;
String ip;
/*
...
*/
}
static LogEntry logEntry;
#Override
public Object visit(ParseTree tree) {
/* Setup logentry used by all visitors (this case, there is only a single visitor...)*/
logEntry = new LogEntry();
/* visit */
final Object o = super.visit(tree);
/* do something with the results */
System.out.println(logEntry.ident1 + ", " + logEntry.dayNum + ", " + logEntry.ip);
return o;
}
StringBuilder stringBuilder;
#Override
public Object visitR(sysParser.RContext ctx) {
logEntry.ident1 = ctx.IDENT().getText();
logEntry.dayNum = ctx.NUM().getText();
return super.visitR(ctx);
}
#Override
public Object visitTime(sysParser.TimeContext ctx) {
logEntry.ip = ctx.getText();
return super.visitTime(ctx);
}
#Override
public Object visitIp(sysParser.IpContext ctx) {
return super.visitIp(ctx);
}
#Override
public Object visitIpfull(sysParser.IpfullContext ctx) {
return super.visitIpfull(ctx);
}
#Override
public Object visitX(sysParser.XContext ctx) {
return super.visitX(ctx);
}
#Override
public Object visitUser(sysParser.UserContext ctx) {
return super.visitUser(ctx);
}
#Override
public Object visitXuser(sysParser.XuserContext ctx) {
return super.visitXuser(ctx);
}
#Override
public Object visitXout(sysParser.XoutContext ctx) {
return super.visitXout(ctx);
}
#Override
public Object visitProto(sysParser.ProtoContext ctx) {
return super.visitProto(ctx);
}
#Override
public Object visitXtra(sysParser.XtraContext ctx) {
return super.visitXtra(ctx);
}
#Override
public Object visitXtra1(sysParser.Xtra1Context ctx) {
return super.visitXtra1(ctx);
}
#Override
public Object visitXtra2(sysParser.Xtra2Context ctx) {
return super.visitXtra2(ctx);
}
#Override
public Object visitXtra3(sysParser.Xtra3Context ctx) {
return super.visitXtra3(ctx);
}
//
}
here is my entire class. I read data from a text file, put them into an aeeaylist. then from that array list i want to show the data on a JTable, when the specific method is called.But is doesnt show anything
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* #author George
*/
import java.awt.*;
import java.util.ArrayList;
//import java.io.FileInputStream;
//import java.io.FileNotFoundException;
//import java.io.EOFException;
//import java.io.IOException;
//import java.io.ObjectInputStream;
/*import java.io.File;
import java.lang.IllegalStateException;
import java.util.NoSuchElementException;
import java.util.Scanner;
import javax.swing.JOptionPane;*/
import java.io.*;
//import java.util.Locale;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
public class Company extends JFrame {
private ArrayList<Employee> emp=new ArrayList<Employee>();
//Employee[] list=new Employee[7];
public void getEmployees(Employee emplo){
emp.add(emplo);
}
/*public void openTxt(){
try {
Scanner input=new Scanner(new File("Employees.txt"));
}
catch(FileNotFoundException e){
JOptionPane.showMessageDialog(null, "File Not Found.");
System.exit(1);
}
}*/
public void doRead() throws Exception{
//ArrayList<Employee> emp=new ArrayList<Employee>() ;
//Employee[] emp=new Employee[7];
//read from file
File data = new File("src/Employees.txt");
BufferedReader read = new BufferedReader(new FileReader(data));
String input;
int i = 0;
//int salesmen = 0;
while ((input = read.readLine()) != null) {
String [] lineParts = input.split(",");
/**
* the following block converts some of the strings inputted to
* the appropriate vartypes.
*/
String EmpNo=(lineParts[0]);
String type=lineParts[10];
String PostalCode = (lineParts[5]);
int phone = Integer.parseInt(lineParts[6]);
short DeptNo = (short) Integer.parseInt(lineParts[8]);
double Salary;
short card = (short) Integer.parseInt(lineParts[11]);
int dtype=0;
if(type.equals("FULL TIME")){
dtype=1;
}
else if(type.equals("SELLER")){
dtype=2;
}
else
dtype=3;
/**
* Creates employee instances depending on their type of employment
* (fulltime=1, parttime=3, salesman=2)
*/
switch (dtype) {
case 1 :
//empNo,firstname, lastname, address, city, PostalCode, phone,
//email, deptcode,Jobtype, salary, TimecardId, hoursW
Salary = Double.parseDouble(lineParts[10]);
emp.add(new FullTimeEmployee(EmpNo,lineParts[1], lineParts[2], lineParts[3],
lineParts[4], PostalCode, phone,
lineParts[7], DeptNo,type,Salary, card, 0.0));
i++;
break;
case 2 :
Salary = Double.parseDouble(lineParts[10]);
ArrayList<Orders> orders=new ArrayList<Orders>();
Salary = Double.parseDouble(lineParts[10]);
emp.add(new Salesman(EmpNo,lineParts[1], lineParts[2], lineParts[3],
lineParts[4], PostalCode, phone,
lineParts[7], DeptNo,type,Salary, card, 0.0, orders));
i++;
break;
case 3 :
Salary = Double.parseDouble(lineParts[10]);
emp.add(new PartTimeEmployee(EmpNo,lineParts[1], lineParts[2], lineParts[3],
lineParts[4], PostalCode, phone,
lineParts[7], DeptNo,type,Salary, card, 0.0));
i++;
break;
default :
break;
}
}
}
public ArrayList<Employee> getArray(){
return emp;
}
//test methodos gia tin proti epilogi-den deixnei tipota omws sto JTable ????
public /*JTable */ void getOptionA(){
ArrayList<Employee> list=getArray();
/*String[] columnNames = {"Code","First Name","Last Name","Address","Cisty","Postal Code","Phone","Email",
"Dept Code","Salary","Time Card","Hours"};*/
/* Object[][] data;
*/
JTable table = new JTable();
DefaultTableModel model = new DefaultTableModel();
table.setModel(model);
model.setColumnIdentifiers(new String[] {"Code","First Name","Last Name","Address","City","Postal Code","Phone","Email",
"Dept Code","Salary","Time Card","Hours"});
for( Employee current : list){
model.addRow(new Object[] {current.getCode(),current.getName(),current.getSurname(),
current.getAddress(),current.getCity(),current.getTK(),
current.getPhone(),current.getMail(),current.getDeptCode(),
current.getSalary(),current.getCard(),current.getHours()
});
}
/*JScrollPane scrollPane = new JScrollPane(table);
table.setFillsViewportHeight(true);*/
//return table;
table.setPreferredScrollableViewportSize(new Dimension(500,50));
table.setFillsViewportHeight(true);
JScrollPane scrollPane = new JScrollPane(table);
add(scrollPane);
}
public void showOptionA(){
getOptionA();
Company gui =new Company();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.setVisible(true);
gui.setSize(600, 400);
}
}
I call showOptionA() from a JButton located on another JFrame Class.
private void showEmployeesActionPerformed(java.awt.event.ActionEvent evt) {
Results showEmp=new Results();
//showEmp.setVisible(true);
//showEmp.setOptions(1);
Company company=new Company();
/*JTable table=company.getOptionA();
JScrollPane scrollPane = new JScrollPane(table);
table.setFillsViewportHeight(true);
scrollPane.setViewportView(table);
table.setVisible(true);*/
company.showOptionA();
}
Basically i have a "main"JFrame with different options, and each button,representing a different option, calls the appropriate option method from Company Class.
When i click on the button "Show Employees Status". i want it to show the JTable above. Instead a new Frame opens but is blank??
EDIT: if i change showOptionA() to static, and then just call it inside showEmployeesActionPerformed , ( which is located in class PayrollForm)
public static void showOptionA(){
Company gui =new Company();
gui.getOptionA();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.setVisible(true);
gui.setSize(600, 400);
}
i now see the columns but with no data(empty)
This has nothing to do with calling revalidate as recommended by another and likely has all to do with calling a method on the wrong object. In your showEmployeesActionPerformed method you create a new Company object, one that is not visualized. The key is to call this method on the proper reference, on the visualized GUI. You do this by passing a reference to the visualized GUI object into the class that is wanting to call methods on it. This can be done via a setCompany method:
setCompany(Company company) {
this.company = company);
}
or via a constructor parameter.
I think the reason is your method showOptionA():
first you add your JTable and Model to your Company Object, which is your Frame. But right after it, you create a new Company Object, set the wanted Frame settings and show that object instead of your first Company object, where the table is.
You just could leave the gui thing out, and set DefaultCloseOperation directly on your Company object and set it visible true.
Some other suggestions:
You also should set the size of your frame, before you set it visible true.
And getters normally just give back the related object, instead of putting it on some list.
Might be there are some more mistakes in it, but it should at least show your table.
Call revalidate() after adding anything to JTable (or its model).
Ok problem Solved!. The problem was in getOptionA() method.
i set the model of the Jtable, but not the option to be visible. SO thats why it appeared empty. I corrected this by moving
try {
//try to read from text file
doRead();
}
catch(Exception e){
JOptionPane.showMessageDialog(null, "An Exception has Occured! The application will now close.");
System.exit(0);
}
table.revalidate();
add(scrollPane);
setVisible(true);
setSize(600, 400);
up,from showOptionA, up to getOptionA() method. By doing this showOptionA becomes useless(and thus i deleted it). Now i call getOptionA from the showEmployeesActionPerformed and its all ok :). Thanks to all people who replied to my wuestion, and special thanks to Hovercraft Full Of Eels . He helped me undeestand why the table wasnt appearing the way i wanted it to