Ebean / Play Framework 2.1 issues fetching a parent/children tree structure - orm

I encountered a problem when trying to fetch a tree structure (parent/children elements in one table).
Using the class below, starting with the root node, I can use getSubNodes() and will receive its children. However, if I then call getSubNodes() on a child node which has itself child nodes in the db, getSubNodes() will return an empty list (the db data contains multiple tree levels)
#Entity
#Table(name = "navigation_items")
public class NavigationItem extends Model {
public static NavigationItemFinder find = new NavigationItemFinder();
private static final long serialVersionUID = 1L;
#Id
private UUID id;
#Column (unique=true)
private String key;
#ManyToOne
private NavigationItem parentNode;
#OneToMany (mappedBy="parentNode", cascade=CascadeType.ALL)
private List<NavigationItem> subNodes;
#Column
#Required
private String title;
#Column
private String subtitle;
#ManyToOne
private PageBlock page;
...
public List<NavigationItem> getSubNodes() {
return subNodes; // <- This crazily but provenly only returns results on the first level of the hierarchy...
}
If I however do not return the subnodes property using the getter, but instead use a finder query, all works as expected and the full tree can be retrieved:
List<NavigationItem> result = find.where().eq("parentNode", this).findList();
Logger.debug("NavItem " + getTitle() + "[" + getId() +"]" + ": returning # subnodes: " + result.size());
Am I missing something here, or could this be an(other) ebean bug?

try this
#OneToMany(cascade = CascadeType.REFRESH, optional = false)
#JoinColumn(name = "currentTablecolumnName", referencedColumnName = "ID", nullable = false)

Related

Spring data jpa native query. Insert object

I'm using a native query on my repository. I want to insert object data, but the query doesn't understand the object fields. How to solve this problem, I don't want to have a method with a lot of parameters.
My class:
public class StudentDTO {
private long numberzachetka;
private String fio;
private Date entrydate;
private int course;
private int numbergroup;
private long specialty;
private long faculty;
//getters, setters..}
Native query:
#Query(value ="insert into student(numberzachetka,fiostudent,entrydate,course,numbergroup,specialtykey,facultynumber)" +
" values" +
" (:student.numberzachetka,:student.fio,:student.entrydate,:student.course,:student.numbergroup,:student.specialty,:student.faculty)", nativeQuery = true)
void addNewStudent(#Param("student") StudentDTO studentDTO);
Student entity:
#Entity
#Table(name="student")
public class Student {
#Id
#Column(name="numberzachetka", nullable = false)
private long numberzachetka;
#Column(name="fiostudent", nullable = false, length = 100)
private String fio;
#Temporal(TemporalType.DATE)
#Column(name = "entrydate", nullable = false)
private Date entrydate;
#Column(name="course", nullable = false)
private int course;
#Column(name="numbergroup", nullable = false)
private int numbergroup;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "specialtykey", nullable = false)
private Specialty specialty;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "facultynumber", nullable = false)
private Faculty faculty;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "student")
private Set<Performance> performances;
#Query(value ="insert into student(numberzachetka,fiostudent,entrydate,course,numbergroup,specialtykey,facultynumber)" +
" values" +
" (:student.numberzachetka,:student.fio,:student.entrydate,:student.course,:student.numbergroup,:student.specialty,:student.faculty)", nativeQuery = true)
void addNewStudent(#Param("student") StudentDTO studentDTO);
This is not the right way to save value in db using JPA repository.
If you want to save value in DB there is very simpler way, follow below steps:
Copy all the values from the studentDTO to Student object using below code.
Student student = new Student();
BeanUtils.copyProperties(studentDto,student);
create StudentRepository interface
#Repository public interface StudentRepository extends
JpaRepository<Student, Long> { }
#Autowired above Repository in the service class
#Autowired private StudentRepository studentRepository;
Save the Student object using repository predefined save method.
studentRepository.save(student);
These are the minimal change you want to save object in db, instead of going through native SQL query.
Within the entity you could have a constructor accepting the DTO. The constructor transfers the DTO attributes to the fields of the entity. Then you can just use the JpaRepository save method. You don't need the select statement. Keep in mind to add also an empty constructor which is required by JPA.
Another Idea which I use in my projects is to use mapstruct to transfert attributes from DTOs to entities.
The way you present your idea is not possible. JPA does not know how to map a DTO to an entity.

Cant get hibernate to update object without EntityExistException being thrown

I am trying to do a bidirectional one to one relation and when updating the AccountExtrasModel on the first save it works fine but when updating I get either an error or the sql statements adds an insert and then a delete instead of an update.
import lombok.*;
import javax.persistence.*;
#ToString
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "Account")
public class AccountModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Account_ID;
#Column(nullable = false, updatable = false)
private String name;
#Column(nullable = false, unique = true, updatable = false)
private String email;
#Column(nullable = false, updatable = false)
private String password;
#OneToOne(mappedBy = "accountModel", cascade = CascadeType.ALL, orphanRemoval = true)
private AccountExtrasModel accountExtras;
public AccountModel addExtras(AccountExtrasModel accountExtrasModel) {
accountExtrasModel.setAccountModel(this);
this.setAccountExtras(accountExtrasModel);
return this;
}
}
import lombok.*;
import javax.persistence.*;
#Setter
#Getter
#NoArgsConstructor
#ToString
#Entity
#Table(name = "AccountExtras")
public class AccountExtrasModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private AccountModel accountModel;
#Lob
private String description;
private String[] myVideos;
private String[] likedVideos;
private String imageReference;
}
If i change the #MapsId to #JoinColumn in AccountExtrasModel then i get the desired result but what its doing is inserting a new row and linking it to acccount and then deleting the old row instead of doing an update.
This is the error im getting:
{
"timestamp": "2018-04-26T18:19:01.657+0000",
"status": 500,
"error": "Internal Server Error",
"message": "A different object with the same identifier value was already associated with the session : [com.alttube.account.models.AccountExtrasModel#1]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.alttube.account.models.AccountExtrasModel#1]",
"path": "/update_account"
}
What can i do to get the desired result which is to simply perform an update on the accountExtrasModel to the corresponding account to which it belongs?
If you update an AccountModel instance by setting a new instance of AccountExtrasModel, as you do in addExtras(), then it is normal to have a delete+insert. By updating the id you would end with orphaned records.
When you set new values on AccountExtrasModel instance, check if accountExtras is initialized. If not, do the addExtras() stuff. If it is, do not replace it, just change it, so hibernate will generate an update on the extras table (but keep the record id unchanged).
Instead of use the method save try use the method merge, because in case of object exist in database the framework will update this object.
Case the object don't exist, the framework will insert as normal.
Regards!

Best way to get aggregate function result inside the entity object

Many time I have to get the SQL aggregate query result inside the Entity Object itself. As of now I could able to achive the same by the following code
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> q = cb.createTupleQuery();
Root<Test> c = q.from(Test.class);
q.multiselect(c, cb.count(c));
q.groupBy(c.get("type"));
TypedQuery<Tuple> t = em.createQuery(q);
List<Tuple> resultList = t.getResultList();
List<Test> list = new ArrayList<>();
for(Tuple tuple : resultList){
Test te = (Test) tuple.get(0);
te.setQuantity((long)tuple.get(1));
list.add(te);
}
But I want to know what could be the best way. My Test Entity is as
#Entity
#Table(name = "test")
public class Test {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "type")
private Integer type = 0;
#Transient
private long quantity;
}
If you cannot use #Formula then I'd suggest create a database view basic on your select and mapping an additional entity to that. You can then map this to your existing entity using either as a #OneToOne or by using #SecondaryTable.
This has the added advantage of being JPA compliant (i.e. not using Hibernate's propreitary #Formula) and would look something like:
#Entity
#Table(name = "test")
public class Test {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "type")
private Integer type = 0;
#OneToOne//or via secondary table
private TestSummaryInfo summaryInfo;
public long getQuantity(){
return summaryInfo.getQuantity();
}
}
Summary mapped to a view:
#Entity
#Table(name = "vw_test_summary")
public class TestSummaryInfo {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "quantity")
private Long quantity;
}

JPA #ElementCollection generates strange unique key

I have an entity class PositionOrdering which contains an element collection:
#ElementCollection(targetClass = Position.class, fetch = FetchType.EAGER)
#CollectionTable(name = "POSITION_ORDERING_POSITION",
joinColumns = #JoinColumn(name = "position_ordering_id"))
#OrderColumn
List<Position> positions = new ArrayList<>();
When hibernate generates the database structure, it looks like this:
CREATE TABLE wls.position_ordering_position
(
position_ordering_id bigint NOT NULL,
positions_id bigint NOT NULL,
positions_order integer NOT NULL,
...
}
It's ok and exactly what I was expected. But it also generate a unique contsraint on positions_id column. It is strange, because the position id should be unique only per ordering, so any of the following unique keys would be ok:
position_ordering_id + positions_order
position_ordering_id + positions_id
But not on the single column of positions_id.
Because the constraint is generated automatically, I can't ignore or remove it simply.
Can I configure my collection to create correct unique constraint or at least not to create any?
UPDATE:
As for request, here is the skeleton of the Position entity:
#Entity
#SequenceGenerator(name = EntityBase.SEQUENCE_NAME,
sequenceName = "POSITION_ID_SEQ")
#Table(name = "position")
public class Position extends EntityBase {
// Lots of fields, like row, column number, and type, etc.
}
Where EntityBase is a simple class with some utility function and with Id:
#MappedSuperclass
public abstract class EntityBase implements Serializable, Cloneable {
public static final String SEQUENCE_NAME = "SEQUENCE_GENERATOR";
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = SEQUENCE_NAME)
protected Long id;
//..
}
#ElementCollection is used for mapping basic types or #Embedded classes, not entities. From the documentation
An ElementCollection can be used to define a one-to-many relationship to an Embeddable object, or a Basic value (such as a collection of Strings).
Since Position is an #Entity, you should map it as #OneToMany or #ManyToMany. I don't know the exact reason why are you getting that unique key generated, but I guess you can expect unpredictable results if you use the annottion in a was that it was not intended for.
As Predrag Maric described it in the accepted answer, the problem was that Position was not an `Embeddable'. My solution was:
I created a support class which wraps the Position into an #Embeddable entity:
#Embeddable
//#Table(name = "position_ordering_position")
public class PositionOrderingPosition {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "position_id", nullable = false)
private Position position;
public PositionOrderingPosition() {
}
public PositionOrderingPosition(Position position) {
this.position = position;
}
public Position getPosition() {
return position;
}
}
Also I changed the Element collection to this:
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "POSITION_ORDERING_POSITION",
joinColumns = #JoinColumn(name = "position_ordering_id"))
#OrderColumn
List<PositionOrderingPosition> positions = new ArrayList<>();
Now it creates the same table, but with the right constraints.

How to do a Inner-Join in JPA Criteria?

I'm using Netbeans to program a webservice REST that returns a JSON response, I am also using JPA Criteria to create the querys. I have two Entities that looks like this:
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id_user")
private Integer idUser;
#Size(max = 45)
#Column(name = "username")
private String username;
#Size(max = 45)
#Column(name = "password")
private String password;
#Size(max = 45)
#Column(name = "email")
private String email;
#OneToMany(mappedBy = "idUser")
private Collection<Comment> commentCollection;
public class Comment implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "id_comment")
private Integer idComment;
#Column(name = "id_thesis")
private Integer idThesis;
#Size(max = 250)
#Column(name = "comment")
private String comment;
#Column(name = "cdate")
#Temporal(TemporalType.DATE)
private Date cdate;
#JoinColumn(name = "id_user", referencedColumnName = "id_user")
#ManyToOne
private User idUser;
}
Both entities with sets and gets. I want to do a query like this:
SELECT * FROM Comments c INNER JOIN User u WHERE c.id_user = u.id_user;
but in the JPA Criteria language, I've had many problems trying to make it work but i don't get it yet.
This is the code that I'm using for the join
AbstractFacade.java
public Join<User, Comment> getCommentInfo() {
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery q = cb.createQuery();
Root<User> r = q.from(User.class);
Join<User, Comment> j = r.join("commentCollection", JoinType.INNER);
Query query = getEntityManager().createQuery(q);
return (Join<User, Comment>) query.getResultList();
}
UserFacadeREST.java
#GET
#Path("test")
#Produces({"application/json"})
public Join<User, Comment> getCommentInfoREST() {
return getCommentInfo();
}
This error is shown when I test the method:
java.util.Vector cannot be cast to javax.persistence.criteria.Join
Please help me with that, I do not know if the sentence join is wrong or how to solve the cast properly.
Edit: I add the next lines to the getCommentInfo() method to see the content of the list.
q.select(j.get("username"));
List results = query.getResultList();
Iterator iter = results.iterator();
while (iter.hasNext()){
System.out.println(iter.next());
}
Error: The attribute [username] is not present in the managed type [EntityTypeImpl#1000979996:Comment.
In the case of getResultList(), the javadocs state that it returns an java.util.List (see here: http://docs.oracle.com/javaee/5/api/javax/persistence/Query.html#getResultList%28%29 ), that Vector implements.
The result type, aka what's in the list, depends on the criteria projection or, in a JPQL Query, of the from statement.
In your case, because you don't do projection, I think it should return a List<User>.
For your information, and if you are using JPA 2.0, you can also use TypedQuery which could avoid that (ugly !) cast : http://www.javabeat.net/typedquery-jpa-2/