I am trying to get the versions field within my MachineGroup Entity that is ManyToMany relationship. I am trying to fetch it in a custom serializer using ObjectMapper but for some reason I always get the LazyInitializationException - org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.altair.autoTester.entities.machinegroup.MachineGroup.versions, could not initialize proxy - no Session.
MachineGroup
#Entity
#Table(name="machine_groups")
#Getter
#Setter
#AllArgsConstructor(access = AccessLevel.PUBLIC)
#NoArgsConstructor
public class MachineGroup {
#Id
#GeneratedValue(strategy= GenerationType.AUTO, generator = "machine_groups_seq")
#SequenceGenerator(name = "machine_groups_seq", allocationSize = 1, initialValue = 2)
#Column(name = "id")
private long id;
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "machine_groups_to_versions",
joinColumns = #JoinColumn(name = "machine_group_id"),
inverseJoinColumns = #JoinColumn(name = "version_id"))
#JsonManagedReference(value="machineGroups-versions")
private Set<Version> versions = new HashSet<>();
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "machine_groups_to_tr_types",
joinColumns = #JoinColumn(name = "machine_group_id"),
inverseJoinColumns = #JoinColumn(name = "tr_type_id"))
private Set<TrType> trTypes = new HashSet<>();
}
Version.java
#Entity
#Table(name="versions")
#Getter
#Setter
public class Version {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "versions_seq")
#SequenceGenerator(name = "versions_seq", allocationSize = 1)
#Column(name = "id")
private long id;
#Column(name = "name", unique = true)
private String name;
#Column(name = "creation_time")
private Date creationTime;
#Column(name = "exe_file")
#Lob
private Blob exeFile;
#JsonBackReference(value="machineGroups-versions")
#ManyToMany(mappedBy = "versions", cascade = CascadeType.MERGE)
private Set<MachineGroup> machineGroups = new HashSet<>();
public Version(){};
public Version(String name) {
this.name = name;
}
}
MachineGroupSerializer.java
public class MachineGroupSerializer extends StdSerializer<MachineGroup> {
public MachineGroupSerializer() {
this(null);
}
public MachineGroupSerializer(Class<MachineGroup> t) {
super(t);
}
#Transactional
#Override
public void serialize(MachineGroup machineGroup,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeNumberField("id",machineGroup.getId());
jsonGenerator.writeObjectField("versions", machineGroup.getVersions().stream().map(Version::getId).collect(Collectors.toSet()));
jsonGenerator.writeObjectField("trTypes", machineGroup.getTrTypes().stream().map(TrType::getId).collect(Collectors.toSet()));
jsonGenerator.writeEndObject();
}
}
TrTypes.java
#Table(name = "tr_types", indexes = {
#Index(name = "tr_types_type_name_uindex", columnList = "type_name", unique = true)
})
#Entity
#AllArgsConstructor(access = AccessLevel.PUBLIC)
#NoArgsConstructor
public class TrType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "type_name", nullable = false)
private String typeName;
}
I put the JsonManagedReference and JsonBackReference so I won't have infinite loop calling one another, not sure if it's causing any issues.
Calling the getTrTypes() function works but has the EAGER fetchType which I do not want to add to the versions collection.
What can cause this lazy exception to occur and how can I prevent it?
Also the function calling the serialzier within my MachineGroupService has the #Transactional annotation, I saw something that is related to the hibernate session that might be close at the serializer level.
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);
A.class
#Getter
#Entity
#Table(name = "A")
public class A extends JpaEntity<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "aId", nullable = false)
private Long id;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "aId")
#Setter
private Set<relationship_table_between_a_and_b> bOfa;
}
relationship_table_between_a_and_b.class
#Entity
#Table(name = "relationship_table_between_a_and_b")
#Getter
public static relationship_table_between_a_and_b extends JpaEntity<relationship_table_between_a_and_b.Id> {
#EmbeddedId
#Setter
private Id id;
#Embeddable
#Getter
public static class Id implements Serializable {
#Setter
#Column(name="aId", nullable = false)
private Long aId;
#Setter
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name="bId", nullable = false)
private B b;
}
}
B.class
public static B extends JpaEntity<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "bId", nullable = false)
private Long id;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "relationship_table_between_b_and_a",
joinColumns = #JoinColumn(name = "bId", nullable = false),
inverseJoinColumns = #JoinColumn(name = "aId", nullable = false))
#Setter
#JsonIgnoreProperties("bOfa")
private List<A> as;
}
I tried several ways but didn't solve the stackoverflowerror
Could not solve the infinite reference that occurs when as of class B refers to bToa of a again.
I want to solve this problem... please help
I've been trying to get my head around how I can create a common tag asset library with hibernate, but I can't get it to work.
I want it to look behave something like this:
I want Subscription and Notification to share the same unique tag library.
I have tried with a #ManyToMany annotation. But I don't think that's the way to do it. In my best case scenario I want Hibernate to automatically see if a tag is already present, and then just use that id. But I'm also okay with first creating tags and then linking them. How is the best way to go about this with Hibernate?
Here's my current code:
Subscription.class
#Data
#Entity
public class Subscription {
#Id
#GeneratedValue
#Column(name = "subscription_id")
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "subscription_tag",
joinColumns = { #JoinColumn(name = "subscription_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private List<Tags> tags = new ArrayList<>();
private String subscriberString;
}
Tags.class
#Data
#Entity
#Table(
name = "notification_tags",
uniqueConstraints = #UniqueConstraint(columnNames = "tag")
)
public class Tags {
#Id
#GeneratedValue
#Column(name = "tag_id")
private Long id;
private String tag;
}
Notification.class
#Data
#Entity(name = "Notification")
#Table(name = "notification")
#TypeDef(
name = "jsonb-node",
typeClass = JsonNodeStringType.class
)
public class Notification extends NotificationBase {
#Id
#GeneratedValue
#Column(name = "notification_id")
private Long id;
// This is when the object was created
private Date updatedTime = new Date();
private Date timestamp;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "notification_tag",
joinColumns = { #JoinColumn(name = "notification_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private List<Tags> tags = new ArrayList<>();
#Type(type = "jsonb-node")
#Column(columnDefinition = "NVARCHAR(4000)")
private JsonNode customJson;
}
I had some relations wrong. You should only have one Class own the relation, but the other class should reference back to that relation.
I also used #JsonManagedReference and #JsonBackReference to avoid circular referencing when Serializing to json.
When I want to search for all Subscriptions with a tag I did not need to search for the tag that is in all Subscriptions, but the other way around, all Subscriptions with a certain tag. See my code below.
SubscriptionRepository.class (to find all Subscription with a certain tag)
public interface SubscriptionRepository extends CrudRepository<Subscription, Long>{
List<Subscription> findAllByTag(Tag tag);
}
Subscription.class
#Data
#Entity
public class Subscription {
#Id
#GeneratedValue
#Column(name = "subscription_id")
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "subscription_tag",
joinColumns = { #JoinColumn(name = "subscription_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private Set<Tags> tags = new HashSet<>();
private String subscriberString;
}
Tag.class
#Data
#Entity
#Table(
name = "notification_tags",
uniqueConstraints = #UniqueConstraint(columnNames = "tag")
)
public class Tag {
#Id
#GeneratedValue
#Column(name = "tag_id")
private Long id;
private String tag;
#ManyToMany(mappedBy = "tags", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JsonBackReference
private Set<Subscription> subscriptions = new HashSet<>();
#ManyToMany(mappedBy = "tags", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JsonBackReference
private Set<Notification> notifications = new HashSet<>();
public Tag() {
}
#Builder
public Tag(String tag) {
this.tag = tag;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Tag tag1 = (Tag) o;
return Objects.equals(tag, tag1.tag);
}
#Override
public int hashCode() {
return Objects.hash(super.hashCode(), tag);
}
}
Notification.class
#Data
#Entity(name = "Notification")
#Table(name = "notification")
#TypeDef(
name = "jsonb-node",
typeClass = JsonNodeStringType.class
)
public class Notification extends NotificationBase {
#Id
#GeneratedValue
#Column(name = "notification_id")
private Long id;
// This is when the object was created
private Date updatedTime = new Date();
private Date timestamp;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "notification_tag",
joinColumns = { #JoinColumn(name = "notification_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private Set<Tags> tags = new HashSet<>();
#Type(type = "jsonb-node")
#Column(columnDefinition = "NVARCHAR(4000)")
private JsonNode customJson;
}
I have the following hibernate mappings:
Class Folders:
#Entity
#Table(name = "FOLDER")
public class Folders implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "folder_seq")
#Column(name = "ID_FOL")
private Long id;
#Column(name = "NOFOLDER")
private String noFolder;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "M_DOMAIN_DN", joinColumns = {
#JoinColumn(name = "FK_FOLDER", referencedColumnName = "ID_FOL") }, inverseJoinColumns = {
#JoinColumn(name = "FK_DOMAIN", referencedColumnName = "ID_DOM") })
private List<Domain> domain = new ArrayList<Domain>();
/** GETTER + SETTER **/
}
Class Domain :
#Entity
#Table(name = "DOMAIN")
public class Domain {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "domain_seq")
#Column(name = "ID_DOM")
private Long id;
#Column(name = "CODE")
private String code;
#Column(name = "LABEL")
private String label;
public Long getId() {
/** GETTER + SETTER **/
}
Method that deletes an entity of type Folder:
public void deleteFolder(Long id) {
try {
entityManager.remove(getFolderById(id));
entityManager.flush();
} catch (PersistenceException e) {
}
}
I tried to delete the entity Folder but is not deleted in the database. Deleting manually in the database works fine, looking for help trying to solve this issue.