I'm building a small eclipse rcp with a little bit of JPA. Now something strange happens:
I create some TopCategories with some SubCategories, this works as intended. The inserts are printed in the log. I close my application and now the problem raises up:
The Categories have a relation to books
Book.java
#Entity
public class Book implements Serializable, PropertyChangeListener {
private static final long serialVersionUID = 4646743297687986216L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private boolean active = true;
#Temporal(TemporalType.TIMESTAMP)
private Date updated;
#Lob
private Set<Group> allowedGroups;
#Column(columnDefinition = "TEXT")
private String text;
private BookType type;
#ManyToOne
private TopCategory topCategory;
#ManyToOne
private SubCategory subCategory;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private BookAttachment attachment;
#Transient
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
// ordinary getter/setter
#PrePersist
#PreUpdate
private void updateUpdated() {
this.updated = new Date();
}
}
After restart and querying Book with this select b from Book all SubCategories which aren't used getting deleted. If a SubCategory has a relation to Book it stays in my DB. Why this occures?
Category.java
#MappedSuperclass
public class Category implements Serializable {
private static final long serialVersionUID = 6091963773161164543L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#Temporal(TemporalType.TIMESTAMP)
private Date updated;
#Enumerated(EnumType.STRING)
private CategoryType type;
#Transient
private List<Snippet> snippets = new LinkedList<Snippet>();
// ordinary getter/setter
#PrePersist
#PreUpdate
public void updateUpdated() {
this.updated = new Date();
}
}
TopCategory.java
#Entity
public class TopCategory extends Category {
#OneToMany(cascade = CascadeType.ALL)
private List<SubCategory> subCategories;
public TopCategory() {
setName("");
setSubCategories(new ArrayList<SubCategory>());
}
public List<SubCategory> getSubCategories() {
return subCategories;
}
public void setSubCategories(List<SubCategory> subCategories) {
this.subCategories = subCategories;
}
#Override
public void setType(CategoryType type) {
super.setType(CategoryType.topCategory);
}
SubCategory.java
#Entity
public class SubCategory extends Category {
#ManyToOne(cascade = CascadeType.ALL)
private TopCategory topCategory;
public TopCategory getTopCategory() {
return topCategory;
}
public void setTopCategory(TopCategory topCategory) {
this.topCategory = topCategory;
}
#Override
public void setType(CategoryType type) {
super.setType(CategoryType.subCategory);
}
}
I'm using Eclipselink 2.1.1.
Regards
Alright - I've found the problem: One of the result lists from my model is modified from a contentprovider - this is were not used SubCategories are removed, in case that the entity objects weren't detached, it caused the entitymanger to update.
Related
I have an Order entity and OrderProduct. I want to show order details on frontend and of course order products in it. So how to fetch product object in OrderProduct JSON. I'm missing product object in products array. I don't need order object one more time and i think it going to be a infinite recursion stuff with it. :)
My Order entity:
#Entity
#Getter
#Setter
#Table(name ="orders")
public class Order{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
private BigDecimal totalPrice;
#OneToMany(mappedBy = "order", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonManagedReference(value="orders")
private List<OrderProduct> products = new ArrayList<>();
private int userId;
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date date = new Date();
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date deliveryDate;
#Enumerated(EnumType.STRING)
private OrderType orderType;
}
My OrderProduct entity:
#Entity
#Setter
#Getter
public class OrderProduct {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JsonBackReference(value="product")
#JoinColumn(name = "product_id")
private Product product;
#ManyToOne
#JsonBackReference(value="orders")
#JoinColumn(name = "order_id")
private Order order;
private Integer quantity;
}
Product entity:
#Entity
#Getter
#Setter
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
private double price;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL)
#JsonManagedReference(value="ingredients")
private List<Ingredient> ingredients = new ArrayList<>();
#OneToMany(mappedBy = "product",fetch = FetchType.EAGER)
#JsonManagedReference(value="product")
private List<OrderProduct> products = new ArrayList<>();
private String fileName;
}
This can help annotate one of your entity clases with
#JsonIdentityInfo(
property = "id",
generator = ObjectIdGenerators.PropertyGenerator.class
)
Every time when JSON serialization go in circles object data will be replaced with object id or orher field of entity for your choose.
You can use #JsonViewannotation to define the fields that you need to serialize to JSON
How it works:
You need define class with interfaces. For example:
public class SomeView {
public interface id {}
public interface CoreData extends id {}
public interface FullData extends CoreData {}
}
Mark entity fields with #JsonView(<some interface.class>)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(SomeView.id.class)
private Long id;
#Column(nullable = false)
#JsonView(SomeView.CoreData.class)
private String username;
#Column(nullable = false)
#JsonView(SomeView.FullData.class)
private String email;
}
Annotate endpoint with #JsonView(<some interface.class>)
#GetMapping()
#JsonView(<some interface.class>)
public User getUser() {
return <get user entity somwhere>
}
In case #JsonView(SomeView.id.class) you will get this JSON:
{
id: <some id>
}
In case #JsonView(SomeView.CoreData.class):
{
id: <some id>,
username: <some username>
}
In case #JsonView(SomeView.FullData.class):
{
id: <some id>,
username: <some username>,
email: <some email>
}
#JsonView also works with embeded objects and you can annotate one field with multiply views classes - #JsonView({SomeView.FullData.class, SomeOtherView.OtherData.class})
In your case i think you should annotate all the fields you need except:
#OneToMany(mappedBy = "product",fetch = FetchType.EAGER)
#JsonManagedReference(value="product")
private List<OrderProduct> products = new ArrayList<>();
in Product
to avoid circular serialization
Or as alternative you can just use DTO classes or seralize oject to JSON manualy (https://thepracticaldeveloper.com/java-and-json-jackson-serialization-with-objectmapper/)
This can be done by my library beanknife
// This configure generate a class named ProductInfo which has the same shape with Product without property "products"
#ViewOf(value = Product.class, genName="ProductInfo", includePattern = ".*", excludes = {"products"})
class ProductInfoConfigure {}
// This configure generate a class named OrderProductRelation with the same shape of OrderProduct.
// But it has not order property and the type of its product property is change to ProductInfo generated above.
#ViewOf(value = OrderProduct.class, genName="OrderProductRelation", includePattern = ".*", excludes = {"order"})
class OrderProductRelationConfigure {
#OverrideViewProperty("product")
private ProductInfo product;
}
// This configure generate a class named OrderDetail with the same shape of Order.
// But the type of its products property is change to List<OrderProductRelation>
#ViewOf(value = Order.class, genName="OrderDetail", includePattern = ".*")
class OrderDetailConfigure {
#OverrideViewProperty("products")
private List<OrderProductRelation> products;
}
will generate these classes:
class ProductInfo {
private Long id;
private String name;
private double price;
private List<Ingredient> ingredients; // it is not processed because you have not provide the class Ingredient
private String fileName;
}
public class OrderProductRelation {
private Long id;
private ProductInfo product;
private Integer quantity;
}
public class OrderDetail {
public Long id;
private BigDecimal totalPrice;
private List<OrderProductRelation> products;
private int userId;
private Date date = new Date();
private Date deliveryDate;
private OrderType orderType;
}
Then
Order order = ...
OrderDetail orderDetail = OrderDetail.read(order);
// serialize the otherDetail instead of order.
List<Order> orders = ...
List<OrderDetail> orderDetails = OrderDetail.read(orders);
// serialize the orderDetails instead of orders.
Possible problems:
I doesn't use Lombok, so Lombok may need to be adapted because it change the byte code on the fly. But it is not a big problem, I will try to adapt it if someone commit the issue and provide enough use cases.
The generated class does not inherit the annotation on the original class. In next release I will provide a sulotion. At this moment, as a workaround, we can use custom method to convert the property manually. such as
#ViewOf(value = Order.class, genName="OrderDetail", includePattern = ".*")
class OrderDetailConfigure {
#OverrideViewProperty("products")
private List<OrderProductRelation> products;
#OverrideViewProperty("orderType")
public static String orderType(Order source) {
return source.getOrder().name();
}
}
The generated class will be changed to
public class OrderDetail {
public Long id;
private BigDecimal totalPrice;
private List<OrderProductRelation> products;
private int userId;
private Date date = new Date();
private Date deliveryDate;
private String orderType;
}
Update
Version 1.2.0 released. Add support of annotation inheritance.
#ViewOf(value = Order.class, genName="OrderDetail", includePattern = ".*")
#UseAnnotation({DateTimeFormat.class, Enumerated.class, JsonProperty.class})
class OrderDetailConfigure {
#OverrideViewProperty("products")
private List<OrderProductRelation> products;
}
generate
public class OrderDetail {
public Long id;
private BigDecimal totalPrice;
private List<OrderProductRelation> products;
private int userId;
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date date;
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date deliveryDate;
#Enumerated(EnumType.STRING)
private OrderType orderType;
}
Those are my classes:
#Entity
#Table(name="assessment")
public class AssesmentProperties {
#Id
#Column(name="AssessmentId")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long AssessmentId;
#Column(unique=true,nullable=false)
private String AssessmentName;
private String AssessmentLevel;
private String Specialization;
private int time;
private String keywords;
private int NoOfSections;
//getters and setters
}
#Embeddable
public class SettingsPrimary implements Serializable {
private Long AssessmentId;
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long Section;
//getters and setters
}
#Entity
#Table(name="section")
public class SectionProperties {
#EmbeddedId
private SettingsPrimary PrimaryKey;
private String SectionType;
private int Weightage;
private int time;
private int NoOfQuestions;
//getters and setters
}
In the table section I need to create assessment_id as FK to assessment table and set cascade on delete. I have tried to do it with different ways but without success.
Maybe this could help you.
#Entity
#Table(name="section")
public class SectionProperties {
#Id
private Long PrimaryKey;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "assessment_id", referencedColumnName="AssessmentId")
private AssesmentProperties AssesmentProperties;
private String SectionType;
private int Weightage;
private int time;
private int NoOfQuestions;
//getters and setters
}
I changed the SectionProperties id to a Long and mapped the AssessmentProprties into a ManytoOne prop.
This way, always that a AssessmentProperties binded to a Section will be deleted, the associated SectionProperties will too.
I am using the Java ModelMapper library to map DTOs to ORM #Entity objects. I have the following test set up:
public class MapperTest {
#Autowired
private ModelMapper mapper;
#Autowired
private TicketRepository ticketRepo;
#Test
public void testTicket() {
Ticket ticket = ticketRepo.findById(4).get();
TicketDTO dto = mapper.map(ticket, TicketDTO.class);
assertThat(dto.getEquipmenDescription()).isEqualTo(ticket.getEquipment().getDescription());
assertThat(dto.getEquipmentNotes()).isEqualTo(ticket.getEquipmentNotes());
assertThat(dto.getId()).isEqualTo(ticket.getId());
assertThat(dto.getNotes()).isEqualTo(ticket.getNotes());
assertThat(dto.getOracleID()).isEqualTo(ticket.getOracleID());
assertThat(dto.getPropertyID()).isEqualTo(ticket.getPropertyID());
assertThat(dto.getNotes().size()).isEqualTo(ticket.getNotes().size());
for (TicketNoteDTO note : dto.getNotes()) {
assertThat(note.getId()).isNotEqualTo(0);
assertThat(note.getIssueDate()).isNotNull();
assertThat(note.getUserUserName()).isNotEmpty();
}
}
}
This fails with the following error:
org.modelmapper.MappingException: ModelMapper mapping errors:
1) Converter org.modelmapper.internal.converter.CollectionConverter#501c6dba failed to convert java.util.List to java.util.List.
The following are my Entity and corresponding DTOs. Getters and setters are omitted for brevity.
Ticket
#Entity
#Table(name = "ticket")
public class Ticket {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "equipment_notes")
private String equipmentNotes;
#Column(name = "is_open")
private boolean isOpen;
#Column(name = "oracle_id")
private String oracleID;
#ManyToOne
#JoinColumn(name = "equipment_id")
private EquipmentCategory equipment;
#Column(name = "property_id")
private String propertyID;
#OneToMany(mappedBy = "ticket")
private List<TicketNote> notes = new ArrayList<>();
}
TicketNote (getters and Setters omitted for brevity)
#Entity
#Table(name = "ticket_note")
public class TicketNote {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "ticket_id")
private Ticket ticket;
#ManyToOne
#JoinColumn(name = "user_id")
private AppUser user;
#Column(name = "issue_date")
private LocalDate issueDate;
#Column(name = "oracle_contact")
private String oracleContact;
#Column(name = "issue_resolved")
private boolean issueResolved;
}
TicketDTO
public class TicketDTO {
private int id;
private String equipmentNotes;
private boolean isOpen;
private String oracleID;
private String equipmenDescription;
private String propertyID;
private List<TicketNoteDTO> notes = new ArrayList<>();
}
TicketNoteDTO
public class TicketNoteDTO {
private int id;
private String userUserName;
private LocalDate issueDate;
private String oracleContact;
private boolean issueResolved;
}
I have some experience with the ModelMapper library, but I am not sure what the issue is. Any advice is appreciated.
Thanks.
i am trying to create a bidirectional one to many relationship.
#Entity
#XmlRootElement
#NamedQueries({
#NamedQuery(name = Company.FIND_ALL, query = "select c from Company
})
public class Company {
public static final String FIND_ALL = "Company.findAll";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String email;
private String name;
private String phoneNumber;
#OneToMany(mappedBy = "company")
private List<Place> places;
private long millisSince1970;
private boolean deleted;
public Company() {
}
#PrePersist
public void addMillisPrePersist() {
millisSince1970 = Instant.now().getEpochSecond();
deleted = false;
}
#PreUpdate
public void addMillisPreUpdate() {
millisSince1970 = Instant.now().getEpochSecond();
}
}
Place class:
#Entity
#XmlRootElement
#NamedQueries({
#NamedQuery(name = Place.FIND_ALL, query = "select p from Place p")
})
public class Place {
public static final String FIND_ALL = "Place.findAll";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private Type type;
private String email;
private String name;
private String city;
private String address;
private String phoneNumber;
private String latitude;
private String longitude;
private String workingHours;
#ManyToOne
#JoinColumn(name = "company_id", referencedColumnName = "id", nullable = false)
private Company company;
#OneToMany(mappedBy = "place")
private List<SpecialOffer> specialOffers;
#OneToMany(mappedBy = "place")
private List<Event> events;
private long millisSince1970;
private boolean deleted;
public Place() {
}
#PrePersist
public void addMillisPrePersist() {
millisSince1970 = Instant.now().getEpochSecond();
deleted = false;
}
#PreUpdate
public void addMillisPreUpdate() {
millisSince1970 = Instant.now().getEpochSecond();
}
}
And here is simple resource:
#GET
#Path("{companyId}")
#Produces({MediaType.APPLICATION_JSON})
public Company getCompany(#PathParam("companyId") int id) {
return entityManager.find(Company.class, id);
}
In my database i have Company and Place tables, in the Place table i have a foreign key column named company_id, so when i try to get some Company which has some corresponding Place glassfish returns http status 500 internal server error without any exception, and server logs are empty, thus i can not debug or understand the cause of this problem. If i try to get the company which doesn't have any places it returns it without any problem. So what am i doing wrong?
P.S. i think my question is similar to this one Glassfish: HTTP 500 Internal Server Error without any exception which unfortunately doesn't have any responses
I have tables with composited primary key.
Server(key=ServerId)
ServerId|Name
1 |server1
2 |server2
ParentObj(key=ServerId+Code)
ServerId|Code |Title
1 |code1|value1
1 |code2|value2
2 |code1|Value2b
ChildObj(key=ServerId+Code+Name)
ServerId|Code |Name |Value
1 |code1|prop1|val1
1 |code1|prop2|val2
1 |code2|prop1|val1b
2 |code1|prop3|val3
This is Java beans I have.
#Entity #Table(name="ParentObj") #Access(AccessType.FIELD)
#IdClass(value=ParentObj.PK.class)
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
#XmlRootElement
public class ParentObj {
#Id private long serverId;
#Id private String code;
private String title;
public long getServerId() { return serverId; }
public String getCode() { return code; }
public String getTitle() { return title; }
public static class PK implements Serializable {
private static final long serialVersionUID = 1L;
private long serverId;
private String code;
public long getServerId() { return serverId; }
public void setServerId(long id) { serverId=id; }
public String getCode() { return code; }
public void setCode(String code) { this.code=code; }
}
}
#Entity #Table(name="ChildObj") #Access(AccessType.FIELD)
#IdClass(value=ChildObj.PK.class)
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
#XmlRootElement
public class ChildObj {
#Id private long serverId;
#Id private String code;
#Id private String name;
private String value;
// public getter+setters for each field
public static class PK implements Serializable {
private static final long serialVersionUID = 1L;
private long serverId;
private String code;
private String name;
public long getServerId() { return serverId; }
public void setServerId(long id) { serverId=id; }
public String getCode() { return code; }
public void setCode(String code) { this.code=code; }
public String getName() { return name; }
public void setName(String name) { this.name=name; }
}
}
I have been trying "everything" to create OneToMany mapping(ParentObj->ChildObj) but nothing seem to work. I don't need ManyToOne(ParentObj<-ChildObj) link but that's ok if one must be defined.
This is a legacy database so I cannot insert an auto_increment identity column or create extra join table between parent and childs.
This annotation is conceptually what I want but multiple join columns is not accepted by OpenJPA2.x library.
// from parent to zero or more childs
#OneToMany(fetch=FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="server_id", referencedColumnName="server_id"),
#JoinColumn(name="code", referencedColumnName="code")
})
private List<ChildObj> properties;
Edit, answer
OneToMany, ManyToOne and EmbeddedId annotations works. I have only tried reading existing rows but its fine for now. Later I try update+insert+delete tasks.
public class ParentObj {
#EmbeddedId ParentObj.PK pk;
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, mappedBy="parent", orphanRemoval=true)
private List<ChildObj> childs;
public PK getPK() { return pk; }
public void setPK(PK pk) { this.pk=pk; }
public List<ChildObj> getChilds() { return childs; }
...
#Embeddable #Access(AccessType.FIELD)
public static class PK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(nullable=false) private long serverId;
#Column(nullable=false) private String code;
..getters+setters+hashCode+equals functions
}
}
public class ChildObj {
#EmbeddedId ChildObj.PK pk;
#ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.PERSIST, optional=false)
#JoinColumns({
#JoinColumn(name="serverid", referencedColumnName="serverid", nullable=false),
#JoinColumn(name="code", referencedColumnName="code", nullable=false)
})
private ParentObj parent;
public PK getPK() { return pk; }
public void setPK(PK pk) { this.pk=pk; }
public long getServerId() { return pk.getServerId(); }
public String getCode() { return pk.getCode(); }
public String getName() { return pk.getName(); }
...
#Embeddable #Access(AccessType.FIELD)
public static class PK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(nullable=false) private long serverId;
#Column(nullable=false) private String code;
#Column(nullable=false) private String name;
..getters+setters+hashCode+equals functions
}
}
The easiest way to do this is to create an association from ChildObj to ParentObj similar to the following:
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumns({
#JoinColumn(name = "serverId", referencedColumnName = "serverId"),
#JoinColumn(name = "code", referencedColumnName = "code")})
private ParentObj parentObj;
and then define the #OneToMany association in ParentObj like this:
#OneToMany(mappedBy = "parentObj", fetch=FetchType.LAZY)
private List<ChildObj> children;
I would also recommend that you define your composite keys as #Embeddable classes, used as #EmbeddedId references in the Entities. These embeddable PK classes should be separate classes (not inner classes), as you will use them separately to query the related Entities, and serialisation of inner classes can cause problems