Cascading not working in oneToMany, with eclipseLink - mysql

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.

Related

Transactional Spring Boot test with MySQL 8 rolls back transaction, but row gets inserted anyway

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

Spring Data REST persistance MySQL one-to-many embedded objects update(PUT/PATCH)

I have two Classes: Object and ObjectProperty, they are connected as one to many relation. So Object has some HashSet of ObjectParameters.
public Set<SOPParameter> parameters = new HashSet<>();
It Spring Data REST performs POST perfectly, but in case of PUT for replacement or PATCH for updating object with particular id, parameters do not affected at all.
What is a way to fix it?
Answering my own question:
First, annotation orphanRemoval has to be set to true
#OneToMany(cascade = {CascadeType.ALL,CascadeType.PERSIST,CascadeType.MERGE}, mappedBy = "sop", orphanRemoval=true)
public Set<SOPParameter> parameters = new HashSet<>();
Second, handler for setParameters have to be stated:
public void setParameters(HashSet<SOPParameter> set) {
if (set != null) {
this.parameters.clear();
this.parameters.addAll(set);
}
}

Persisting a Many-to-Many entity by adding to a List of entities

I am getting a list of entities and attempting to add more values to it and have them persist to the data base... I am running into some issues doing this... Here is what I have so far...
Provider prov = emf.find(Provider.class, new Long(ID));
This entity has a many to many relationship that I am trying to add to
List<Organization> orgList = new ArrayList<Organization>();
...
orgList = prov.getOrganizationList();
So I now have the list of entities associated with that entity.
I search for some entities to add and I place them in the orgList...
List<Organization> updatedListofOrgss = emf.createNamedQuery("getOrganizationByOrganizationIds").setParameter("organizationIds", AddThese).getResultList();
List<Organization> deleteListOfOrgs = emf.createNamedQuery("getOrganizationByOrganizationIds").setParameter("organizationIds", DeleteThese).getResultList();
orgList.addAll(updatedListofOrgss);
orgList.removeAll(deleteListOfOrgs);
As you can see I also have a list of delete nodes to remove.
I heard somewhere that you don't need to call persist on such an opperation and that JPA will persist automatically. Well, it doesn't seem to work that way. Can you persist this way, or will I have to go throught the link table entity, and add these values that way?
public class Provider implements Serializable {
#Id
#Column(name="RESOURCE_ID")
private long resourceId;
...
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name="DIST_LIST_PERMISSION",
joinColumns=#JoinColumn(name="RESOURCE_ID"),
inverseJoinColumns=#JoinColumn(name="ORGANIZATION_ID"))
private List<Organization> organizationList;
...//getters and setters.
}
The link table that links together organizations and providers...
public class DistListPermission implements Serializable {
#Id
#Column(name="DIST_LIST_PERMISSION_ID")
private long distListPermissionId;
#Column(name="ORGANIZATION_ID")
private BigDecimal organizationId;
#Column(name="RESOURCE_ID")
private Long resourceId;
}
The problem is that you are missing a cascade specification on your #ManyToMany annotation. The default cascade type for #ManyToMany is no cascade types, so any changes to the collection are not persisted. You will also need to add an #ElementDependent annotation to ensure that any objects removed from the collection will be deleted from the database. So, you can change your Provider implementation as follows:
#ManyToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#ElementDependent
#JoinTable(name="DIST_LIST_PERMISSION",
joinColumns=#JoinColumn(name="RESOURCE_ID"),
inverseJoinColumns=#JoinColumn(name="ORGANIZATION_ID"))
private List<Organization> organizationList;
Since your Provider class is managed, you should not need to merge the entity; the changes should take effect automatically when the transaction is committed.

Having trouble getting a Hibernate JUnit test to generate a constraint exception

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.

DOM4J createQuery not eagerly fetching

I have an entity which contains a OneToMany relationship, with eager fetching, with a second entity. This second entity has two OneToOne relationships, with eager fetching also, to a third and fourth class. The OneToOne relationships are unidirectional.
I am calling createQuery() from a DOM4J session sending in "from entity" as the HQL. In the return I get the second entity but it contains only the IDs of the third and fourth entities instead of the complete contents. To me it looks like those third and fourth entities are not being eagerly fetched. I can't reproduce the code exactly but here is the most relevant parts.
#Entity
public class Event extends EventParent {
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinColumn(name="eventId")
#org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Pair> pairs=new HashSet<MarPair>();
}
#Entity
public class Pair extends PairParent {
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Info info;
#OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Results results;
}
#Entity
public class Info {
private String name;
private Date time;
}
#Entity
public class Results {
private String name;
private Date time;
}
Finally here is the code I am using for the query:
public void retrieve() {
String hqlQry = "from Event";
Session session = dom4JSessionFactory.getCurrentSession();
Session dom4jSession = session.getSession(EntityMode.DOM4J);
List results = dom4jSession.createQUery(hqlQuery).list();
}
As I mentioned, from this query I am getting back an integer for the value of info and results which is the key to the info and results table instead of the actual data being retrieved from the info and results table.
Relevant Information:
Spring 2.5.4
Hibernate 3.2.6
Hibernate Annotations 3.3.1.GA
dom4JSessionFactory is of type org.springframework.orm.hibernate3.LocalSessionFactoryBean
The entity "Event" is actually the 7th class down in a class hierarchy (don't know if this matters or not)
I did leave out a lot of information hoping that it was not necessary. If there is something else you would need to venture a guess as to why it isn't working, please let me know.
Turns out this is a bug with the Hibernate version we are using. What I had to do was to change embed-xml to true after Hibernate generated the HBM files. This was done using the "replace" ant function.