I'm using Hibernate 4.0.1.Final with a MySQL 5.5 database. I'm writing a Java console app. In a JUnit test, I'm having trouble getting a test to fail. Here's my models under test …
#Entity
#Table(name = "ic_domain")
public class Domain {
#Id
#Column(name = "DOMAIN_ID")
private String domainId;
#OneToOne(fetch = FetchType.EAGER, targetEntity = Organization.class)
#JoinColumn(name = "ORGANIZATION_ID")
private Organization org;
and
#Entity
#Table(name = "ic_organization")
public class Organization {
#Id
#Column(name = "ORGANIZATION_ID")
private String organizationId;
My problem is, in my JUnit test, I'm trying to create a foreign key that doesn't exist, expecting things to fail upon saving, but they never do. Here's the JUnit test
#Test
public void testSaveDomainWithUnmathcedOrg() {
final Organization org = createDummyOrg();
// Create an org id that doesn't exist.
org.setOrganizationId("ZZZZ");
final Domain domain = new Domain();
final String id = UUID.randomUUID().toString().replace("-", "");
domain.setDomainId(id);
domain.setName(org.getName());
domain.setOrg(org);
m_domainDao.saveOrUpdate(domain);
} // testSaveDomainWithUnmatchedOrg
The code of the DAO is
public void saveOrUpdate(final Domain domain) {
final Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(obj);
} // saveOrUpdate
Shouldn't things fail at "session.saveOrUpdate"? How do I make them? I don't want to commit my data in the JUnit test because I don't want to pollute the underlying database with dummy data, but if that is the only way, so be it.
You don't have to commit, but you need to flush the session to trigger execution of SQL statements:
m_domainDao.saveOrUpdate(domain);
sessionFactory.getCurrentSession().flush();
Then you can roll the transaction back if you don't want to pollute the database.
Why do you expect things to fail?
You create a new Domain and save it. Should work as expected.
Domain has a mapping to Organization. You have not stated any #Cascade options so I would guess no insert is made to the Organisation table. Add save-update Cascade options on the one-to-one and I would expect both Domain and Organisation are saved as expected with no issues.
Only issue I can see is if you tried to save the Organisation as the parent Domain would not exist in the database. But then you wouldn't want to do that anyway.
I would suggest maintaining a seperate DB for testing and using something like DBUnit to set up the data for each test run.
http://www.dbunit.org/
I typically use this with Spring's transactional test support and Hibernate's DDL value to to 'create' so the test database automatically updates with every schema change.
Related
Given the following service method in a Spring Boot application:
#Transactional
public void updateCategory(long categoryId, CategoryData categoryData) {
final Category category = categoryRepository.findById(categoryId).orElseThrow(EntityNotFoundException::new);
category.setName(categoryData.getName());
}
I know how to instruct Mockito to mock the categoryRepository.findById() result.
However, I couldn't figure out yet: Is it possible to verify that category.setName() was called with the exact argument of categoryData.getName()?
You are looking for Mockito.verify, and a test looking like:
#ExtendWith(MockitoExtension.class)
public class CategoryServiceTest {
#Mock
CategoryRepository categoryRepository;
#InjectMocks
CategoryService categoryService;
#Test
public void testUpdateCategoryMarksEntityDirty() {
// given
long categoryId = 1L;
Category category = mock(Category.class);
String newCategoryName = "NewCategoryName";
when(categoryRepository.findById(categoryId)).thenReturn(Optional.of(category));
// when
categoryService.updateCategory(categoryId, new CategoryData(newCategoryName));
// then
verify(category, times(1)).setName(newCategoryName);
}
}
I must, however, advise against this style of testing.
Your code suggests that you are using a DB Access library with dirty-checking mechanism (JPA / Hibernate?). Your test focuses on the details of interaction with your DB Access layer, instead of business requirement - the update is successfully saved in the DB.
Thus, I would opt for a test against a real db, with following steps:
given: insert a Category into your DB
when: CategoryService.update is called
then: subsequent calls to categoryRepository.findById return updated entity.
Normally when I do a Spring Boot app with Spring Data JPA, in the tests the transactions rollback automatically and the test database is not changed. This behavior isn't working, however, with MySQL8.
I have a trivial POJO called Category.
#Entity
#Table(name = "categories")
public class Category {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "category_id")
private Integer id;
#Column(name = "category_name")
private String name;
// ... constructors, getters and setters, etc, omitted ...
}
Here's my even more trivial repository interface:
public interface CategoryRepository extends JpaRepository<Category,Integer> {
}
I have an existing database and here are my application.properties settings to access it:
spring.datasource.url=jdbc:mysql://localhost:3306/dashboard
spring.datasource.username=admin
spring.datasource.password=not_password
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
There are currently 10 categories in the table. My test checks for them, and another test inserts a new one.
#DataJpaTest
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
class CategoryRepositoryTest {
#Autowired
private CategoryRepository dao;
#Test
void findAll() {
List<Category> categories = dao.findAll();
assertEquals(10, categories.size());
}
#Test
void insertCategory() {
Category cat = new Category("Misc");
assertNull(cat.getId());
cat = dao.save(cat);
assertNotNull(cat.getId());
System.out.println(cat);
}
}
Note that #DataJpaTest already includes #Transactional. The output of the second test is:
2019-10-03 14:26:48.844 INFO 91485 --- [ Test worker] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [DefaultTestContext#67e4b73d testClass = CategoryRepositoryTest, testInstance = com.kousenit.simpledemo.dao.CategoryRepositoryTest#3913544f, testMethod = insertCategory#CategoryRepositoryTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration#7314dd45 testClass = CategoryRepositoryTest, locations = '{}', classes = '{class com.kousenit.simpledemo.MyApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer#3c6df497, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer#351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer#8b9f8fd, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer#15acb0c6, [ImportsContextCustomizer#76c5962 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer#21f27cf2, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer#67568498, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer#0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager#4a3861f3]; rollback [true]
Hibernate:
insert
into
categories
(category_name)
values
(?)
Category{id=11, name='Misc'}
2019-10-03 14:26:48.880 INFO 91485 --- [ Test worker] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test: [DefaultTestContext#67e4b73d testClass = CategoryRepositoryTest, testInstance = com.kousenit.simpledemo.dao.CategoryRepositoryTest#3913544f, testMethod = insertCategory#CategoryRepositoryTest, ...
The problem is, after the test is over, I still have the new category in the database. With H2, the transactions rolled back and it wasn't there, but with MySQL 8 even though the rollback is happening, the inserted item remains.
What's different here? How do I fix it so the insert is reset at the end of the test?
As transaction needs to be commited explicitly. Therefore I think you need to set property of autocommit to false. like this
<property name="hibernate.connection.autocommit">false</property>
Make your test methods public because, from documentation:
Due to the proxy-based nature of Spring’s AOP framework, calls within
the target object are, by definition, not intercepted. For JDK
proxies, only public interface method calls on the proxy can be
intercepted.
If the method is not public, no error is thrown.
I suppose you are using the default (JDK proxies, not CGLIB)
I am not sure if that is your problem but I was getting the same behavior because I was using "peek" inside a stream which was creating a thread and its own transaction and when tests were run all together it was leaving rows inside the database. When they were run one by one that did not happen. Be sure that no new threads are being created and not controlled inside your code
I see the following exception message in my IDE when I try to get lazy initialized entity (I can't find where it is stored in the proxy entity so I can't provide the whole stack trace for this exception):
Method threw 'org.hibernate.LazyInitializationException' exception. Cannot evaluate com.epam.spring.core.domain.UserAccount_$$_jvste6b_4.toString()
Here is a stack trace I get right after I try to access a field of the lazy initialized entity I want to use:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:286)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)
at com.epam.spring.core.domain.UserAccount_$$_jvstfc9_4.getMoney(UserAccount_$$_jvstfc9_4.java)
at com.epam.spring.core.web.rest.controller.BookingController.refill(BookingController.java:128)
I'm using Spring Data, configured JpaTransactionManager, database is MySql, ORM provider is Hibernate 4. Annotation #EnableTransactionManagement is on, #Transactional was put everywhere I could imagine but nothing works.
Here is a relation:
#Entity
public class User extends DomainObject implements Serializable {
..
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "user_fk")
private UserAccount userAccount;
..
#Entity
public class UserAccount extends DomainObject {
..
#OneToOne(mappedBy = "userAccount")
private User user;
..
.. a piece of configuration:
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty(PROP_NAME_DATABASE_DRIVER));
dataSource.setUrl(env.getRequiredProperty(PROP_NAME_DATABASE_URL));
dataSource.setUsername(env.getRequiredProperty(PROP_NAME_DATABASE_USERNAME));
dataSource.setPassword(env.getRequiredProperty(PROP_NAME_DATABASE_PASSWORD));
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
entityManagerFactoryBean.setPackagesToScan(env.getRequiredProperty(PROP_ENTITYMANAGER_PACKAGES_TO_SCAN));
entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
return entityManagerFactoryBean;
}
#Bean
public JpaTransactionManager transactionManager(#Autowired DataSource dataSource,
#Autowired EntityManagerFactory entityManagerFactory) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
jpaTransactionManager.setDataSource(dataSource);
return jpaTransactionManager;
}
.. and this is how I want to retrieve UserAccount:
#RequestMapping(...)
#Transactional()
public void refill(#RequestParam Long userId, #RequestParam Long amount) {
User user = userService.getById(userId);
UserAccount userAccount = user.getUserAccount();
userAccount.setMoney(userAccount.getMoney() + amount);
}
Hibernate version is 4.3.8.Final, Spring Data 1.3.4.RELEASE and MySql connector 5.1.29.
Please, ask me if something else is needed. Thank you in advance!
Firstly, you should understand that the root of the problem is not a transaction. We have a transaction and a persistent context (session). With #Transactional annotation Spring creates a transaction and opens persistent context. After method is invoked a persistent context becomes closed.
When you call a user.getUserAccount() you have a proxy class that wraps UserAccount (if you don't load UserAccount with User). So when a persistent context is closed, you have a LazyInitializationException during call of any method of UserAccount, for example user.getUserAccount().toString().
#Transactional working only on the userService level, in your case. To get #Transactional work, it is not enough to put the #Transactional annotation on a method. You need to get an object of a class with the method from a Spring Context. So to update money you can use another service method, for example updateMoney(userId, amount).
If you want to use #Transactional on the controller method you need to get a controller from the Spring Context. And Spring should understand, that it should wrap every #Transactional method with a special method to open and close a persistent context. Other way is to use Session Per Request Anti pattern. You will need to add a special HTTP filter.
https://vladmihalcea.com/the-open-session-in-view-anti-pattern/
As #v.ladynev briefly explained, your issue was that you wanted to initialize a lazy relation outside of the persistence context.
I wrote an article about this, you might find it helpful: https://arnoldgalovics.com/lazyinitializationexception-demystified/
For quick solutions despite of performance issues use #transactional in your service
Sample:
#Transactional
public TPage<ProjectDto> getAllPageable(Pageable pageable) {
Page<Project> data = projectRepository.findAll(pageable);
TPage<ProjectDto> response = new TPage<>();
response.setStat(data, Arrays.asList(modelMapper.map(data.getContent(), ProjectDto[].class)));
return response;
}
it will get user details for project manager in the second query.
For more advanced solution, you should read the blog post in the #galovics answer.
I used below to fix
sessionFactory.getObject().getCurrentSession()
Create query and get required object
I was also facing the same error while running my springBoot App.
What is the real issue here?
Please check have you autowired the repository at controller level
If first step is correct then please check where ever you have autowired your JPA repository , it should be a part of #Transactional code.
If not please add #Transactional annotation.It will solve your issue.
I was getting this error:
Method threw 'org.hibernate.LazyInitializationException' exception.
This is because currently there is no session present. Hibernate opens a session and closes it, but for "lazy = true" or "fetch = FetchType.LAZY" such fields are populated by proxies. When you try to find the value for such a field, it will attempt to go to the database using the active session to retrieve the data. If no such session can be found, you get this exception.
You can fix it using "lazy=false" or check whether you have used #Transcational properly (try to use this in your service layer than your data access layer), you can also use
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
OR
#Transactional
I am working with EclipseLink and JPA 2.0.
Those are my 2 entities:
Feeder entity:
#Entity
#Table(name = "t_feeder")
public class Feeder implements Serializable {
private static final long serialVersionUID = 1L;
//Staff
#OneToMany(cascade = CascadeType.ALL, mappedBy = "idAttachedFeederFk")
private Collection<Port> portCollection;
//staff
}
Port entity:
#Entity
#Table(name = "t_port")
public class Port implements Serializable {
//staff
#JoinColumn(name = "id_attached_feeder_fk", referencedColumnName = "id")
#ManyToOne
private Feeder idAttachedFeederFk;
//staff
}
And this is my code:
Feeder f = new Feeder();
//staff
Port p = new Port();
p.setFeeder(f);
save(feeder); //This is the function that calls finally persist.
The probleme is that, only feeder is persisted and not the port. Am I missing something? And specially, in which side should I mention the cascading exactly. Given that in my database, the port table is referencing the feeder one with a foreign key.
EDIT
This simple piece of code worked fine with me:
public static void main(String[] args) {
Address a1 = new Address();
a1.setAddress("madinah 0");
Employee e1 = new Employee();
e1.setName("houssem 0");
e1.setAddressFk(a1);
saveEmplyee(e1);
}
I am not sure why you would expect it to work: you are attempting to save a new instance of Feeder which has no connection whatsoever to the newly created Port.
By adding the Cascade to the #OneToMany and calling save(feeder) Eclipse link would if there were an association:
Insert the record for the Feeder.
Iterate the Port collection and insert the relevant records.
As I have noted however, this new Feeder instance has no Ports associated with it.
With regard to your simple example I assume when you say it works that both the new Address and Employee have been written to the database. This is expected because you have told the Employee about the Address (e1.setAddressFk(a1);) and saved the Employee. Given the presence of the relevant Cascade option then both entities should be written to the database as expected.
Given this it should then be obvious that calling save(port) would work if the necessary cascade option was added to the #ManyToOne side of the relationship.
However if you want to call save(feeder) then you need to fix the data model. Essentially you should always ensure that any in-memory data model is correct at any given point in time, viz. if the first condition below is true then it follows that the second condition must be true.
Port p = new Port();
Feeder feeder = new Feeder();
p.setFeeder(f();
if(p.getFeeder().equals(f){
//true
}
if(f.isAssociatedWithPort(p)){
//bad --> returns false
}
This is obviously best practice anyway but ensuring the correctnes of your in-memory model should mean you do not experience the type of issue you are seeing in a JPA environment.
To ensure the correctness of the in-memory data model you should encapsulate the set/add operations.
As the title suggests I am building a web application using Spring Boot and Hibernate for my data access layer and the development is done on InteliJ IDEA 14.1.2.
My Knowledge
This is my first time using Spring Boot, Hibernate and InteliJ. I have built a few small apps to test Spring Boot and Hibernate, but the complexity difference between those and the one I am building now is a bit bigger.
Environment
Regarding my environment, in case it matters, I am running Windows 7 SP1 64bit, MySQL server 5.6.17, InteliJ 14.1.2 and Ubuntu Server 14.04 on a VirtualBox 4.3.26 VM hosting a Redis 3.0.1 server.
Purpose
The purpose of using the above technologies at this point in time is the storage and retrieval of different entities to a MySQL database (Redis is used only for session externalization and sharing among app instances). In other words, I am building my data access layer.
Database
My complete database schema can be found here:
https://dl.dropboxusercontent.com/u/49544122/so/DB.pdf
Source
My Spring Boot application is the following:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import se.domain.cvs.abstraction.dataaccess.AccountRepository;
import se.domain.cvs.domain.AccountEntity;
#SpringBootApplication
#EnableRedisHttpSession
public class Application implements CommandLineRunner {
#Autowired
AccountRepository repository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... strings) throws Exception {
System.out.println("=======================================================");
AccountEntity account = repository.findByEmail("r.franklin#companya.se");
System.out.println("My name is " + account.getFirstName() + " " + account.getLastName());
System.out.println("=======================================================");
}
}
I am using CommandLineRunner interface just to test the bare data access layer without introducing REST endpoints yet.
My configuration is the following in YAML format:
...
# MySQL Database Configuration
spring.datasource:
url: jdbc:mysql://localhost:3306/cvs
username: cvs
password: cvs
driverClassName: com.mysql.jdbc.Driver
spring.jpa:
database: MYSQL
show-sql: true
hibernate.ddl-auto: validate
hibernate.naming-strategy: org.hibernate.cfg.DefaultNamingStrategy
properties.hibernate.dialect: org.hibernate.dialect.MySQL5Dialect
...
The JPA entities are automatically generated with InteliJ and that is where the problems begin. Let's take for example the OrderEntity below (for the sake of brevity I omit some code):
...
#Entity
#Table(name = "order", schema = "", catalog = "cvs")
public class OrderEntity {
...
private int invoiceId;
...
private InvoiceEntity invoiceByInvoiceId;
...
#Basic
#Column(name = "InvoiceID", nullable = false, insertable = false, updatable = false)
public int getInvoiceId() {
return invoiceId;
}
public void setInvoiceId(int invoiceId) {
this.invoiceId = invoiceId;
}
...
#ManyToOne
#JoinColumn(name = "InvoiceID", referencedColumnName = "InvoiceID", nullable = false)
public InvoiceEntity getInvoiceByInvoiceId() {
return invoiceByInvoiceId;
}
public void setInvoiceByInvoiceId(InvoiceEntity invoiceByInvoiceId) {
this.invoiceByInvoiceId = invoiceByInvoiceId;
}
...
}
When trying to run the Spring Boot application I get the following error:
org.hibernate.MappingException: Repeated column in mapping for entity: OrderEntity column: invoiceId (should be mapped with insert="false" update="false")
After doing a little bit of research, I guess the problem is that the invoiceID now has two ways to be set, one through the setInvoiceID() setter and one through the InvoiceEntity object itself that the OrderEntity relates to, which could lead to an inconsistent state. As another user here puts it,
You would do that when the responsibility of creating/udpating the related entity in question isn't in the current entity.
See related post here: Please explain about: insertable=false, updatable=false
Setting the proposed values of the corresponding field (insertable and updateable) to false fixes the error.
My question here is why is this generated the wrong way? My change fixed the error, but I want to make sure that there is no errors in my SQL that lead InteliJ to generate this the wrong way. The complete SQL script can be found here http://pastebin.com/aDguqR1N.
Additionally, when generating the Entities, InteliJ requires a Hibernate config file which I guess Spring Boot generates on its own somewhere else (or uses Java based configuration). Whether I leave it there or delete it, it doesn't seem to affect the app at all. I guess the order taken by SB to read properties overrides it. Is it OK that I just remove it?
Thank you very much for your time and help in advance and sorry for this long post! :)
my advice is to let Spring/Hibernate let generate your db schema for you ( everything including foreign keys and constraints can be generated by Spring.
For me the folloeing approach worked:
in the parent entity(in my case the TblUser):
#OneToMany(targetEntity=TblTracks.class,fetch=FetchType.EAGER,cascade=CascadeType.ALL,mappedBy="tbluser")
private List<TblTracks> tbltracks= new ArrayList<TblTracks>();
where mappedBy points to the Tbluser Entity (private TblUser tbluser) of the child Entity
and in the child entity (in my case TblTracks) like
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="idTblUser",nullable=false)
private TblUser tbluser;