Multiple representations of the same entity in Oracle - java

I have two tables, the first one is TB_RECIPE_DATA, where the PK is the ID_RECIPE field. The second table is TB_RECIPE_ITEM, where the PK is composed of three fields: ID_RECIPE, CD_LOT and CD_PRODUCT. These two tables are related so that a recipe can have multiple items. The problem I'm facing is that when I try to register a recipe with more than one item, I get an error message "InvalidDataAccessApiUsageException: Multiple representations of the same entity". When I register a recipe with just one item, it works.
In the research I've done, many indicate that it's because of Cascade, I've already tried switching to cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH} and it didn't work. The mapping of the tables was done this way:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_RECIPE_DATA", schema = "A_SAMPLE")
public class Recipe {
#Id
#Column(name = "ID_RECIPE")
private Long id;
#Fetch(FetchMode.SUBSELECT)
#OneToMany(mappedBy = "id", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<RecipeItem> items;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_RECIPE_ITEM", schema = "A_SAMPLE")
public class RecipeItem {
#Id
#Column(name = "ID_RECIPE")
private Long id;
#Column(name = "CD_LOT")
private String lot;
#Column(name = "CD_PRODUCT")
private Long code;
#Column(name = "QT_PURCHASE")
private Long purchaseQuantity;
#Column(name = "FL_AVAILABLE")
private Boolean available;
}

The error was happening because when changing, for example, recipe A with its respective items, each item has recipe A as part of the Primary Key, so I would be changing the recipe twice. The solution would be to work the bi-direction for this case. I will share my solution in case anyone experiences a similar problem.
Main class:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_RECIPE_DATA", schema = "A_SAMPLE")
public class Recipe {
#Id
#Column(name = "ID_RECIPE")
private Long id;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private List<RecipeItem> items;
}
Child class:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TB_RECIPE_ITEM", schema = "A_SAMPLE")
public class RecipeItem {
#EmbeddedId
private RecipeItemPk id;
#Column(name = "QT_PURCHASE")
private Long purchaseQuantity;
#Column(name = "FL_AVAILABLE")
private Boolean available;
#ManyToOne
#JoinColumn(name = "ID_RECIPE", insertable = false, updatable = false)
private Recipe recipe;
}
Primary Key class:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(of = { "id", "lot", "code" })
#Embeddable
public class RecipeItemPk {
#Column(name = "ID_RECIPE")
private Long id;
#Column(name = "CD_LOT")
private String lot;
#Column(name = "CD_PRODUCT")
private Long code;
}

Related

How to simulate lombok #data stackoverflow error within unit tests for repository class?

I have a relationship between entities that throws a stack overflow error if the #Data annotation from Lombok is used instead of the individual #Getter and #Setter annotations. This is fixed now, but I would like to write a unit test for it within my repository tests. However, I'm not sure how to achieve that and haven't been able to find samples for it.
Here are my entity classes:
#Entity
#Table(name = "users")
#Builder
//#Getter
//#Setter
#Data
#AllArgsConstructor
#NoArgsConstructor
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "name")
private String name;
#ManyToMany
#JoinTable(
name = "users_hobbies",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "hobby_and_interest_id", referencedColumnName = "id"))
private Set<HobbyAndInterestEntity> hobbyAndInterestEntities;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "hometown_id", referencedColumnName = "id")
private HometownEntity hometownEntity;
#Entity
#Table(name = "hometown")
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
public class HometownEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "city")
private String city;
#Column(name = "country")
private String country;
#OneToMany(mappedBy = "hometownEntity", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = false)
private Set<UserEntity> userEntitySet;
#Data
#AllArgsConstructor
#NoArgsConstructor
public class HobbyAndInterestEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "title")
private String title;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "hobbyAndInterestEntities")
private Set<UserEntity> userEntities;
And here is my test for a case without the exception, which I was aiming to modify to test for the exception scenario:
#Test
void testGetUser() {
UserEntity userEntity = saveUserEntity();
assertTrue(userRepository.findAll().size() > 0);
userEntity = userRepository.findById(userEntity.getId()).orElse(null);
assertNotNull(userEntity);
UserEntity finalUserEntity = userEntity;
assertAll(
() -> assertEquals("anyName", finalUserEntity.getName()),
() -> assertEquals("anyCountry", finalUserEntity.getHometownEntity().getCountry()),
() -> assertTrue(finalUserEntity.getHobbyAndInterestEntities().size() > 0));
finalUserEntity.getHobbyAndInterestEntities().forEach(h -> assertEquals("anyInterest", h.getTitle()));
}
#NotNull
private UserEntity saveUserEntity() {
HometownEntity hometownEntity = HometownEntity.builder().city("anyCity").country("anyCountry").build();
hometownEntity = hometownRepository.save(hometownEntity);
HobbyAndInterestEntity hobbyAndInterestEntity = HobbyAndInterestEntity.builder()
.title("anyInterest")
.build();
hobbyAndInterestEntity = hobbyAndInterestRepository.save(hobbyAndInterestEntity);
Set<HobbyAndInterestEntity> hobbyAndInterestEntities = new HashSet<>();
hobbyAndInterestEntities.add(hobbyAndInterestEntity);
UserEntity userEntity = UserEntity.builder()
.name("anyName")
.hometownEntity(hometownEntity)
.hobbyAndInterestEntities(hobbyAndInterestEntities)
.build();
return userRepository.save(userEntity);
}
So in summary, I know the application is throwing the stack overflow when I have the #Data annotation and so I would like to write a test that would fail for it and pass again when I modify the entity class to use #Getter and #Setter, but not sure what is needed here and would appreciate some guidance, please.
Thank you very much.
Could you check #Data annotation here. #Data is a shortcut for #ToString, #EqualsAndHashCode, #Getter on all fields, #Setter on all non-final fields, and #RequiredArgsConstructor! When you call toString or equals or hashCode method, the relationship entities will query in the database. You can try to review generated source, the relationship entities is used in those methods. I think it can throw a stack overflow error.

CascadeType for updating entity in relation #ManyToOne

I have two Entities in relation #ManyToOne:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "TRIPS")
public class Trip {
#Id
#GeneratedValue
#NotNull
#Column(name = "ID", unique = true)
int tripId;
#OneToMany(
targetEntity = Audit.class,
mappedBy = "tripId",
fetch = FetchType.LAZY
)
private List<Audit> audits = new ArrayList<>();
}
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table
public class Audit {
#Id
#GeneratedValue
#NotNull
#Column(unique = true)
public int auditId;
#ManyToOne
#JoinColumn(name = "TRIP_ID")
public Trip tripId;
}
When I'm trying to:
Audit audit = new Audit();
auditDao.save(audit);
List<Audit> audits = new ArrayList<>();
audits.add(audit);
Trip trip = new Trip(audits);
I get exception: detached entity passed to persist.
I tried to change CascadeType to MERGE, but it didn't help.
Where's the problem. Is there maybe other way?

save/create Entity in Hibernate

I am facing an issue with persisting Observation entity.
The Observation entity has a list of protocols and every protocol has 1 observer. The observer can also be created before or not, so I can have many protocols which have created observers and many protocols which have not created observers.
The desired behavior for me is if I create this observation then observers with id == null will be created and observers with id will be merged.
The issue I am facing is if I specify CascadeType.Merge I can create observation with only the observers which were created before and if I specify CascadeType.Persist/CascadeType.All, I can create observation with only the observers that were not created before.
ObservationEntity:
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Observation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "observation_id", referencedColumnName = "id", nullable = false)
#OrderBy("observer ASC")
#Fetch(FetchMode.SUBSELECT)
private List<Protocol> protocols;
}
ProtocolEntity:
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Protocol {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
#JoinColumn(name = "observer_id", nullable = false)
private Observer observer;
}
ObserverEntity:
#Entity
#Getter
#Setter
#NoArgsConstructor
public class Observer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
private String imoCode;
}
ObservationService:
#Service
public class ObservationService {
#Autowired private ObservationMapper observationMapper;
#Autowired private ObservationRepository observationRepository;
public ObservationDTO create(ObservationDTO observationDTO) {
Observation observationCreated = observationRepository.save(observationMapper.observationDTO2Observation(observationDTO));
return observationMapper.observation2ObservationDTO(observationCreated);
}
}
Error:
org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : sk.adambarca.serverspringboot.model.entity.Interval.protocol -> sk.adambarca.serverspringboot.model.entity.Protocol
Try this:
private List<Protocol> protocols = new ArrayList<>();

How to use a foreign key variable without writing the specific foreign key column in the entity class?

I have the following entity 'User' where the field 'companyId' is a foreign key:
#Entity
#Table(name = "Users")
#Getter #Setter #ToString
public class User {
#Id
#GeneratedValue
private long id;
#Column(name = "company_id")
private Long companyId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "company_id", insertable = false, updatable = false)
private Company company;
The Company entity:
#Entity
#Table(name = "Companies")
#Getter #Setter #ToString
public class Company {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "company", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
private List<User> users;
I removed other irrelevant fields from the classes.
I'm using spring boot data jpa.
My question is how to remove the field 'companyId' and use the company id inside the field 'company' for CRUD functions with the DB.
Simply remove the companyId and make Company writable
#Entity
#Table(name = "Users")
#Getter #Setter #ToString
public class User {
#Id
#GeneratedValue
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "company_id")
private Company company;

Lombok with hibernate

Is this possible? Haven't seen much discussion on it.
Sure! It works great from my experience. Here's an example entity:
#Entity
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class PingerEntity {
// ID
#Id
#Getter
#Setter
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// USER
#Getter
#Setter
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private UserEntity user;
// URL
#Getter
#Setter
#Basic(optional = false)
private String url;
/**
* The number of seconds between checks
*/
#Getter
#Setter
#Basic(optional = false)
private int frequency;
#Getter
#Setter
#Basic(optional = false)
#Enumerated(EnumType.STRING)
public MonitorType monitorType;
}
You can use it also with #Data (and it works !)
#Entity
#Data
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
}
I have never tried Lombok with Hibernate but I don't see why it shouldn't work.
Also, take a look here: http://groups.google.com/group/project-lombok/browse_thread/thread/294bd52d9d8695df/7bc6b0f343831af1?lnk=gst&q=hibernate#7bc6b0f343831af1
Also, Lombok project release notes mention Hibernate explicitely.
A simple example; Library.java:
#Data
#NoArgsConstructor // JPA
#Entity
#Table(name = "libraries")
public class Library {
#Id
#GeneratedValue
private Long id;
#OneToMany(cascade = CascadeType.ALL)
#EqualsAndHashCode.Exclude
// This will be included in the json
private List<Book> books = new ArrayList<>();
#JsonIgnore
public void addBook(Book book) {
books.add(book);
book.setLibrary(this);
}
}
And Book.java:
#Data
#NoArgsConstructor // JPA
#Entity
#Table(name = "books")
public class Book {
#Id
#GeneratedValue
private Long id;
#NotBlank
private String title;
#ManyToOne
#JoinColumn(name = "library_id") // Owning side of the relationship
#EqualsAndHashCode.Exclude
#JsonIgnore // Avoid infinite loops
private Library library;
}

Categories

Resources