How to copy entity with multiple #OneToMany?
I have entities:
Profile.java
#Getter
#Setter
#ToString
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
#EqualsAndHashCode(exclude = {"id"})
#Entity
#Table(name = "profile")
public class Profile implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "profile", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProfileDoc> documents = new ArrayList<>();
#OneToMany(mappedBy = "profile", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Contact> contacts = new ArrayList<>();
#OneToOne(mappedBy = "profileAddress", cascade = CascadeType.ALL, orphanRemoval = true)
private AddressBirth birthPlace;
}
ProfileDoc
#Getter
#Setter
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
#ToString(exclude = "profile")
#EqualsAndHashCode(exclude = {"id", "profile"})
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#Entity
#Table(name = "profile_doc")
public class ProfileDoc implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "doc_code")
private String documentCode;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="profile_id")
private Profile profile;
#OneToMany(mappedBy = "document", cascade = CascadeType.ALL, orphanRemoval = true)
private List<DocFile> files = new ArrayList<>();
}
How to copy Profile(all field) without id?
as I understand it, you need to copy documents without profile_id in ProfileDoc class
UPD:
I want copy entity from another entity(copy) for save to DB
BeanUtils.copyProperties(profile, newProfile);
if I copy with Id, then the original entity will update, not a new one
Related
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.
I have two enteties and I want to be able access one of them from another and vise versa (bidirectional). But sometimes when persisting an order as stopLossOrder it's not saved to position. If you have any ideas of how it can be implemented (if it's possible this way or similar) I would be glad to hear.
Later on I want to add more orders to position entity similarly to 'stopLossOrder'
#Entity(name = "Position")
#Table(name = "positions")
#Getter #Setter
public class PositionEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
orphanRemoval = true)
#JoinColumn(name = "stop_loss_order_id", referencedColumnName = "id")
private OrderEntity stopLossOrder;
#OneToMany(mappedBy = "position",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true)
private Set<OrderEntity> orders = new HashSet<>();
public void setStopLossOrder(OrderEntity stopLossOrder) {
this.stopLossOrder = stopLossOrder;
stopLossOrder.setPosition(this);
}
public boolean addOrder(OrderEntity orderEntity) {
orderEntity.setPosition(this);
return orders.add(orderEntity);
}
}
#Entity(name = "Order")
#Table(name = "orders")
#Getter
#Setter
public class OrderEntity implements Serializable {
#Getter(AccessLevel.NONE)
#Setter(AccessLevel.NONE)
private static final long serialVersionUID = -1462587657644552577L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(optional = false)
private PositionEntity position;
}
Persisting:
if (orderEntity.getType() == OrderType.STOP_MARKET) {
positionEntity.setStopLossOrder(orderEntity);
} else {
boolean isAdded = positionEntity.addOrder(orderEntity);
if (!isAdded)
throw new TradeServiceException("Order with id: " + order.getOrderId() + " already added to position.");
}
orderEntity = orderRepository.save(orderEntity);
I am using SpringBoot, so lets say first, I want to make a Country, and after doing that POST with JSON how can I do other POST to create a City and adding it to the Country created?
Or I cant do it with JSON?
And idk if is a good idea having the FK pointing the name instead of the ID, in my head it works the same bc is an unique key, right?
Thanks!
Country code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "country_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class Country implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<City> cities = new HashSet<>();
}
City code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "city_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class City implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "country_name", referencedColumnName = "name", nullable = false,
foreignKey=#ForeignKey(name = "FK_country_city"))
private Country country;
#OneToMany(mappedBy = "city", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Neighborhood> neighborhoods = new HashSet<>();
}
Neiborhood code:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(name = "neighborhood_name",columnNames = "name")
})
#Getter #Setter #RequiredArgsConstructor #NoArgsConstructor #ToString
public class Neighborhood implements Serializable {
#Id
#Column(updatable = false, nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#NonNull
private String neighborhoodType;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "city_name", referencedColumnName = "name", nullable = false,
foreignKey=#ForeignKey(name = "FK_city_neighborhood"))
private City city;
}
So first you need to do the POST request to create Country object:
{
"name": "USA",
"cities": []
}
Second you need to do the POST request to create the City object and put the field country with the Primary Key (PK):
{
"name": "Huston",
"country": 1,
"neighborhoods": []
}
That's pretty much it actually.
I created two simple entities for trying out the java persistence manytomany mapping. But whatever I try, the jointable won't be populated with a mapping and remains empty.
UserClass:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(targetEntity = Order.class ,fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "users_orders",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "order_id", referencedColumnName = "id")
)
#JsonIgnoreProperties(value = "orderUsers")
private Set<Order> userOrders = new HashSet<>();
}
OrderClass:
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToMany(mappedBy = "userOrders", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "userOrders")
private Set<User> orderUsers = new HashSet<>();
}
I added Getter/Setter/Constructor via Lombok.
Create and save an user. Create an order, add the user and save it. But still the jointable remains empty.
Any ideas?
I am using spring-boot-starter-data-jpa 1.5.1.RELEASE which internally uses hibernate-core 5.0.11.Final
My entity looks like this:
AreaDto
#Entity
#Table(name = "AREA")
#EntityListeners(AuditingEntityListener.class)
public class AreaDto {
#Id
#Column(name = "AREA_ROWID")
private String areaRowId;
#OneToMany(cascade = CascadeType.DETACH)
#JoinColumn(name = "AREA_ROWID")
private Collection<FestivalDto> festival;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<ActionDto> actions;
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "FESTIVAL", joinColumns = {
#JoinColumn(name = "AREA_ROWID", referencedColumnName = "AREA_ROWID")}, inverseJoinColumns = {
#JoinColumn(name = "FESTIVAL_ROWID", referencedColumnName = "FESTIVAL_ROWID")})
private Collection<ActionDto> festivalActions;
}
FestivalDto
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "FESTIVAL")
public class FestivalDto {
#Id
#Column(name = "FESTIVAL_ROWID")
#GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
private Long festivalRowId;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
private Collection<ActionDto> actions = Lists.newArrayList();
}
ActionDto
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "ACTION")
public class ActionDto implements Serializable {
...
#Id
#Column(name = "ACTION_ID")
#GeneratedValue(generator = "ACTION_ID_SEQ")
private Long actionId;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "FESTIVAL_ROWID")
private FestivalDto festival;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
}
I'm trying to make sense of the below ideas:
What is the strategy used by hibernate to decide on the festival_rowid (or festival_row ids) used to get all the associated action? How will hibernate generated SQL query vary if i change festivalActions fetch strategies between LAZY and EAGER? I know about proxying, collection proxying and all, my question is specific to how those sql is generated and how it may have an impact on deciding the value of bind parameter.
Is my mapping accurate or should I be using a multimap for this relationship since an area could have multiple festival and each festival could have multiple actions
Background:
I am getting below error which goes away if I change the fetch type from LAZY to EAGER. Hoping to understand the behaviour for gaining some confidence in the fix. I have read SO and error
org.hibernate.HibernateException: More than one row with the given identifier was found: data.dto.ActionDto#280856b5
This mapping does not make much sense. You can't map festivalActions this way because there is no way to persist the state properly through such a mapping. Also festival in AreaDto should be mapped by the area in FestivalDto. Try the following instead:
#Entity
#Table(name = "AREA")
#EntityListeners(AuditingEntityListener.class)
public class AreaDto {
#Id
#Column(name = "AREA_ROWID")
private String areaRowId;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<FestivalDto> festival;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<ActionDto> actions;
public Collection<ActionDto> getFestivalActions() {
return festival.stream().flatMap(f -> f.actions.stream()).collect(Collectors.toList());
}
}
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "FESTIVAL")
public class FestivalDto {
#Id
#Column(name = "FESTIVAL_ROWID")
#GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
private Long festivalRowId;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
private Collection<ActionDto> actions = Lists.newArrayList();
}
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "ACTION")
public class ActionDto implements Serializable {
...
#Id
#Column(name = "ACTION_ID")
#GeneratedValue(generator = "ACTION_ID_SEQ")
private Long actionId;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "FESTIVAL_ROWID")
private FestivalDto festival;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
}