Hibernate criteria, two entities - java

Lets say I have a query :
SELECT one FROM EntityOne one, EntityTwo two
WHERE one.id = two.otherId AND two.someValue = 2
I'd like to transform it using Criteria tools but don't know to how fetch
thanks in advance

Here is the code on how you can get results using Criteria:
EntityOne.java
#Entity
public class EntityOne {
#Id
#GeneratedValue
private int id;
private String name;
#OneToMany(mappedBy = "entity", cascade=CascadeType.ALL)
private Set<EntityTwo> entities = new HashSet<EntityTwo>();
public EntityOne(String name) {
this.name = name;
}
public void addEntity(EntityTwo entity) {
this.entities.add(entity);
}
// Default constructor, setters & getters
}
EntityTwo.java
#Entity
public class EntityTwo {
#Id
#GeneratedValue
int id;
String name;
#ManyToOne
#JoinColumn(name = "entity_one_id")
private EntityOne entity;
public EntityTwo(String name) {
this.name = name;
}
// Default constructor, setters & getters
}
Code to save some entities to database:
EntityOne eo1 = new EntityOne("Entity eo1");
EntityTwo et1 = new EntityTwo("one");
EntityTwo et2 = new EntityTwo("two");
eo1.addEntity(et1);
eo1.addEntity(et2);
et1.setEntity(eo1);
et2.setEntity(eo1);
EntityOne eo2 = new EntityOne("Entity eo2");
EntityTwo et3 = new EntityTwo("three");
EntityTwo et4 = new EntityTwo("four");
eo2.addEntity(et3);
eo2.addEntity(et4);
et3.setEntity(eo2);
et3.setEntity(eo2);
session.save(eo1);
session.save(eo2);
Now the code for getting records using Criteria:
Criteria criteria = session.createCriteria(EntityOne.class, "e1");
criteria.createAlias("e1.entities", "e2");
criteria.add(Restrictions.eq("e2.name", "two"));
List<EntityOne> entityList = criteria.list();
for (EntityOne entityOne : entityList) {
System.out.println(entityOne.getName());
for (EntityTwo entity : entityOne.getEntities()) {
System.out.println("->" + entity.getName());
}
}
Output will be:
Entity eo1
->two
->one

Related

Entity Manager not auto increasing ID

I'm trying to run a UT but is failing at the #Before method. This is the error:
Caused by: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "UK_PBNJJ4MCIQ51S0SJV9U3J2WQ4_INDEX_5 ON PUBLIC.XACTIVITYCONTENTTYPE(CONTENTTYPE_ID) VALUES (19, 1)"; SQL statement:
insert into XACTIVITYCONTENTTYPE (ACTIVITY_ID, CONTENTTYPE_ID) values (?, ?) [23505-197]
I have an array of object(ActivityEntity) which I'm initializing and persisting in a H2 DB:
for (int i = 0; i < activities.length; i++) {
Date createdDate = new Date();
ActivityEntity activity = new ActivityEntity();
activity.setType(ActivityType.valueOf(properties.getType()));
activity.setLabel(ActivityLabel.valueOf(properties.getLabel()));
activity.setStatus(Status.valueOf(properties.getStatus()));
activity.setDeliveryType(DeliveryType.valueOf(properties.getDeliveryType()));
activity.setSubject(em.find(SubjectEntity.class, subjectId));
activity.setFontSize(FontSize.valueOf(properties.getFontSize()));
activity.setEstimatedTime(ESTIMATED_TIME);
activity.setPlannedTime(properties.getPlannedTime());
activity.setInteractivityType(InteractivityType.valueOf(properties.getInteractivityType()));
activity.setAudience(Audience.valueOf(properties.getAudience()));
activity.setPurpose(Purpose.valueOf(properties.getPurpose()));
activity.setAcademicLevel(AcademicLevel.valueOf(properties.getAcademicLevel()));
activity.setEnvironment(Environment.valueOf(properties.getEnvironment()));
activity.setInstructionMethod(InstructionMethod.valueOf(properties.getInstructionMethod()));
activity.setCreatedBy(CREATED_BY);
activity.setCreatedDate(createdDate);
activity.setModifiedBy(CREATED_BY);
activity.setModifiedDate(createdDate);
activity.setDeprecated(properties.isDeprecated());
activity.setTemplate(properties.isTemplate());
activity.setCurriculumProvider(CurriculumProvider.valueOf(properties.getCurriculumProvider()));
activity.setShowLessonNavigator(properties.isShowLessonNavigator());
activity.setShowHeader(properties.isShowHeader());
activity.setDisplayModuleType(properties.isDisplayModuleType());
activity.setDisplayLabelType(properties.isDisplayLabelType());
activity.setShowFooter(properties.isShowFooter());
activity.setShowPagination(properties.isShowPagination());
activity.setDisplayProgressBar(properties.isDisplayProgressBar());
activity.setDisplayResources(properties.isDisplayResources());
activity.setLanguage(language);
activity.setPrimaryStatus(PrimaryStatus.valueOf(properties.getPrimaryStatus()));
activity.setIntendedDeliveryType(IntendedDeliveryType.valueOf(properties.getIntendedDeliveryType()));
activity.setNextGen(properties.isNextGen());
activity.setExcludeFromSearch(properties.isExcludeFromSearch());
activity.setExcludeFromRecommender(properties.isExcludeFromRecommender());
activity.setTeacherCreated(properties.isTeacherCreated());
activity.setTitle(TITLE + (i + 1), language);
activity.getGrades().addAll(grades);
activity.getStudentGroupings().add(new StudentGroupingEntity(properties.getStudentGroupingId()));
activity.getPedagogicalIntents().add(new PedagogicalIntentEntity(properties.getPedagogicalIntentId()));
activity.getLearnerTypes().add(new LearnerTypeEntity(properties.getLearnerTypeId()));
activity.getContentTypes().add(new ContentTypeEntity(properties.getContentTypeId()));
activities[i] = em.persist(activity);
}
em.flush();
The last set it's the property related to the error. The properties have a value of 19 for the ContentTypeId. Now, this is part of the Activity entity class:
#Entity
#Table(name = "ACTIVITY")
public class ActivityEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_ACTIVITY_ACTIVITY_ID")
#SequenceGenerator(name = "SQ_ACTIVITY_ACTIVITY_ID", sequenceName = "SQ_ACTIVITY_ACTIVITY_ID", allocationSize = 1)
#Column(name = "ACTIVITY_ID")
private Integer id;
//MORE FIELDS LEFT OUT FOR CLARITY
#ManyToMany
#JoinTable(name = "XACTIVITYCONTENTTYPE", joinColumns = { #JoinColumn(name = "ACTIVITY_ID", referencedColumnName = "ACTIVITY_ID") }, inverseJoinColumns = { #JoinColumn(name = "CONTENTTYPE_ID", referencedColumnName="ID") } )
private List<ContentTypeEntity> contentTypes = new ArrayList<>();
}
And here's the ContentTypeEntity class:
#Entity
#Table(name = "CONTENTTYPE")
public class ContentTypeEntity {
#Id
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#Column(name = "SEQ_NUM")
private int seqNum;
public ContentTypeEntity() {
}
public ContentTypeEntity(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSeqNum() {
return seqNum;
}
public void setSeqNum(int seqNum) {
this.seqNum = seqNum;
}
}
If I debug, the ids for ActivityEntity is being generated correctly for each of the 3 objects that i'm putting in the array (ids=[1,2,3]). So i don't understand why the second insert is using the id=1, which is what the exception is implying. If I put one ActivityEntity in the array, everything works correctly.
You haven't pasted in all the code and you cannot assign the return type of em.persist, but i think the issue is probably related to creating multiple instances of contenttype with id 19.
Assuming content type with id 19 is already persisted and you are attempting to just create references to it rather than persist it as you have no cascade in your ManyToMany you can do something like this as this sample test code shows. The transaction are there as I don't know your tx boundaries in your code and just for the purpose of saving the contenttype separately
#Test
public void saveActivities() {
// Tx1 - Persist content type
EntityTransaction tx1 = em.getTransaction();
tx1.begin();
ContentTypeEntity contentType = new ContentTypeEntity(19);
em.persist(new ContentTypeEntity(19));
tx1.commit();
em.detach(contentType);
// Tx2 - Persist activities using a reference to content type
EntityTransaction tx2 = em.getTransaction();
tx2.begin();
for (int i = 0; i < 3; i++) {
ActivityEntity activity = new ActivityEntity();
activity.getContentTypes().add(em.getReference(ContentTypeEntity.class, 19));
em.persist(activity);
}
tx2.commit();
// assertions
}

DTO Query with JPA CriteriaQuery and Hibernate using entity as a DTO constructor creates many selects

hibernate 5.2.10.Final
jpa 2.1
I want to map a projection query to a DTO (Data Transfer Object) with JPA Criteria Query and Hibernate. I specify a constructor that will be applied to the results of the query execution.
If the constructor is for entire entity class, I have multiple of selects instead one(it is a long running process for thousands of records). If the constructor is for a set of params of the Entity then I see only one select in the console. I can't understand where I've mistaken or is it a bug?
public class ServiceDAO {
public List<ServicesDTO> getAllServicesByFilter(ServicesFilter filter) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ServicesDTO> criteria = cb.createQuery(ServicesDTO.class);
Root<ServicesEntity> serviceEntity = criteria.from(ServicesEntity.class);
// here is only one select to get list of services
criteria.select(cb.construct(ServicesDTO.class, serviceEntity.get("active"), serviceEntity.get("providerId"), serviceEntity.get("serviceId")));
// in this case I have multiple selects
//criteria.select(cb.construct(ServicesDTO.class, serviceEntity));
if(filter != null) {
List<Predicate> pcl = new ArrayList<Predicate>();
if(filter.getActive() != null)
pcl.add(cb.equal(serviceEntity.get("active"), filter.getActive()));
if(filter.getProviderId() != null)
pcl.add(cb.equal(serviceEntity.get("providerId"), filter.getProviderId()));
if(filter.getServiceId() != null)
pcl.add(cb.equal(serviceEntity.get("serviceId"), filter.getServiceId()));
criteria.where(pcl.toArray(new Predicate[pcl.size()]));
}
return entityManager.createQuery(criteria).getResultList();
}
}
-
public class ServicesDTO implements Serializable {
private static final long serialVersionUID = 1L;
private Boolean active;
private Integer providerId;
private Integer serviceId;
public ServicesDTO() {}
public ServicesDTO(Boolean active, String providerId, Integer serviceId) {
this.active = active;
this.providerId = Integer.parseInt(providerId);
this.serviceId = serviceId;
}
public ServicesDTO(ServicesEntity service) {
if(service != null) {
this.active = service.isActive();
this.providerId = Integer.parseInt(service.getProviderId());
this.serviceId = service.getServiceId();
}
// getters & setters
}
-
#Entity
#Table
public class ServicesEntity {
#Id
#Column(name = "id", unique = true)
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name = "serviceId", nullable = false)
private int serviceId;
#Column(nullable = false)
private String providerId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="categoryId")
private Categories categoryId;
private boolean active;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "service", cascade = CascadeType.ALL)
private List<Service_Area_Ref> areas = new ArrayList<Service_Area_Ref>();
#ManyToOne(fetch=FetchType.LAZY, optional = true)
#JoinColumn(name="parentCatId")
private Categories parentCatId;
public ServicesEntity() {}
public ServicesEntity(int serviceId) {
this.serviceId = serviceId;
}
// getters & setters
// equals & hashcode
}
Yea, so it does. There is probably not much of a use case for that. Given
#Entity
public class A {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private Integer value;
public class ADto {
private Integer va;
public ADto(A a) {
this.va = a.getValue();
}
public ADto(Integer va) {
this.va = va;
}
Then
tx.begin();
A a1 = new A();
a1.setValue(1);
A a2 = new A();
a1.setValue(2);
em.persist(a1);
em.persist(a2);
tx.commit();
em.clear();
System.out.println("As usual");
em.createQuery("select new dto.ADto(a.value) from A a where a.value <= 2", ADto.class).getResultList();
System.out.println("As A");
em.createQuery("select new dto.ADto(a) from A a where a.value <= 2", ADto.class).getResultList();
gives you
create table A (id integer generated by default as identity (start with 1), value integer, primary key (id))
create table B (id integer generated by default as identity (start with 1), value integer, primary key (id))
insert into A (id, value) values (default, ?)
insert into A (id, value) values (default, ?)
As usual
select a0_.value as col_0_0_ from A a0_ where a0_.value<=2
As A
select a0_.id as col_0_0_ from A a0_ where a0_.value<=2
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?
select a0_.id as id1_0_0_, a0_.value as value2_0_0_ from A a0_ where a0_.id=?
And you don't like the fact that entity A is selected each time for a new ADto instance. It's probably done that way because you could have created a DTO with multiple entities, not just A, like A, B, and C and so how would JPA/Hibernate do that conveniently in a single select statement? While it could select all the attributes and then keep track of which attributes belong to which entities and then construct them and pass them to your DTO so you can deconstruct them that seems like a lot of work for a rare thing. It's probably more efficient and better all around if you select the attributes you want and make a constructor out of whatever that is, as in the first case.
I am using Hibernate 5.3 and also encounter this behaviour. But I found that if using JPA Tuple as a DTO container and multiselect, this problem will not happen. So my final solution is use Tuple to query the result set first and then convert it to DTO manually , something likes:
CriteriaQuery<Tuple> criteria = cb.createTupleQuery();
.......
criteria.multiselect(serviceEntity);
List<ServicesDTO> result = entityManager.createQuery(criteria).getResultList().stream()
.map(t->new ServicesDTO(t.get(0,ServicesEntity.class)))
.collect(toList());

JPA query for getting list of objects which are in one To Many relationship

I have following tables in one to many relationship
Class A {
private Integer id;
private String name;
#OneToMany(mappedBy="a", fetch=FetchType.LAZY)
private List<B> bs;
public A(Integer id, String name, List<B> bs){
this.id = id;
this.name = name;
this.bs = bs;
}
}
Class B {
#ManyToOne
private A a;
private String name;
}
I wanto to write query to get data of class A along with their bs
e.g
Select NEW A(a.id, a.name, a.bs) FROM A a WHERE a.id = 10;
my constructor is of parameter (Integer id, String name, List bs). But it throws error unnable to locate appropriate constructor.
Can you tell me what mistake I am doing. And is this really possible in JPA
I think that you missed the #JoinColumn annotation of JPA. Try this:
Class A {
private Integer id;
private String name;
#OneToMany(mappedBy="a", fetch=FetchType.LAZY)
private List<B> bs;
}
Class B {
#ManyToOne
#JoinColumn(name="aId") // Link 'aId' foreign key <--
private A a;
private String name;
}
Give the full package name of the class new A in your query.
First you need to add the default constructor to you entities
The query is better like this
"Select NEW A(a.id, a.name, a.bs) FROM " + A.class.getSimpleName() + " a WHERE a.id = 10";
Also as msagala said you need to the #JoinColumn(name = "id")

JPA #ManyToOne join table issue

I am working on an authentication model that would suit GlassFish's JDBC Realm requirements.
In this model I have one group which can contain multiple users, but each user can only be in one group (e.g. su, admin, etc.).
I have two entities: Groups.java (for groups) and Credential.java (for users) and intend to feed the generated join table to Glassfish's "Group Table" property.
I am able to persist both Groups and Credential instances, but the required middle table (credential_groups) is not even created, let alone updated.
Below are my entities:
Credential.java:
#Entity
#Access(AccessType.PROPERTY)
#Table(name = "credential")
public class Credential extends MetaInfo implements Serializable {
private String username;
private String passwd;
private Groups group;
private boolean blocked;
private boolean suspended;
public Credential() {
super();
}
public Credential(String createdBy) {
super(Instant.now(), createdBy);
}
public Credential(String createdBy, String username, String passwd) {
this(createdBy);
this.username = username;
this.passwd = passwd;
}
#Id
#Column(name = "username")
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
updateModified();
}
#Column(name = "passwd")
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
updateModified();
}
#ManyToOne(fetch = FetchType.LAZY)
public Groups getGroups() {
return group;
}
public void setGroups(Groups group) {
this.group = group;
group.getCredentials().add(this);
updateModified();
}
#Column(name = "is_blocked")
public boolean isBlocked() {
return blocked;
}
public void setBlocked(boolean blocked) {
this.blocked = blocked;
updateModified();
}
#Column(name = "is_suspended")
public boolean isSuspended() {
return suspended;
}
public void setSuspended(boolean suspended) {
this.suspended = suspended;
updateModified();
}
}
Groups.java:
#Entity
#Access(AccessType.PROPERTY)
#Table(name = "groups")
#NamedQueries({
#NamedQuery(name = "findAllGroups",
query = "SELECT g FROM Groups g order by g.modifiedDate DESC")
})
public class Groups extends MetaInfo implements Serializable {
private String groupName;
private Set<Credential> credentials;
public Groups() {
super();
credentials = new HashSet();
}
public Groups(String groupName) {
this();
this.groupName = groupName;
}
public Groups(String createdBy, String groupName) {
this();
setCreatedBy(createdBy);
this.groupName = groupName;
}
#Id
#Column(name = "group_name")
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
updateModified();
}
#OneToMany(mappedBy = "groups")
#JoinTable(
name = "credential_groups",
joinColumns = #JoinColumn(name = "group_name"),
inverseJoinColumns = #JoinColumn(name = "username")
)
public Set<Credential> getCredentials() {
return credentials;
}
public void setCredentials(Set<Credential> credentials) {
this.credentials = credentials;
}
public void addCredential(Credential c) {
credentials.add(c);
if (c.getGroups() != this) {
c.setGroups(this);
}
}
}
As I said persisting both works (for Groups I have also tried updating, querying), but this error keeps on firing on every operation with either entity:
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Can't write; duplicate key in table '#sql-4d2_54'
Error Code: 1022
Call: ALTER TABLE credential ADD CONSTRAINT FK_credential_GROUPS_group_name FOREIGN KEY (GROUPS_group_name) REFERENCES groups (group_name)
Query: DataModifyQuery(sql="ALTER TABLE credential ADD CONSTRAINT FK_credential_GROUPS_group_name FOREIGN KEY (GROUPS_group_name) REFERENCES groups (group_name)")
Important update:
This is the error that is being thrown even before the pasted above exception:
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'group VARCHAR(255) NOT NULL, PRIMARY KEY (credential, group))' at line 1
Error Code: 1064
Call: CREATE TABLE credential_groups (credential VARCHAR(255) NOT NULL, group VARCHAR(255) NOT NULL, PRIMARY KEY (credential, group))
As requested by Francois Motard, here's my jUnit method that triggers the error (the group is created in another test class):
public class CredentialRepositoryTests {
private final OCGroupsRepository groupRepo;
private final OCCredentialRepository credentialRepo;
public CredentialRepositoryTests() {
groupRepo = new OCGroupsRepository();
credentialRepo = new OCCredentialRepository();
}
...
#Test
public void createCredentialTest(){
//retrieve the group
Groups admin = groupRepo.getByGroupName("admin");
Credential rimma = new Credential("user_creator", "sample_user", "abc123");
admin.addCredential(rimma);
assertTrue(groupRepo.updateGroups(admin));
}
I based my code on the instructions from the EJB 3.0 in Action by Manning and tried to resolve the join table issue based on this stackoverflow answer: How to create join table with JPA annotations?
Can anyone help me have the join table created and updated? Thank you very much.
Resolved by removing the mappedBy attribute from the #OneToMany annotation of the owned entity (this fixed the non-creation of the join table) and by adding the unique attribute (set to true - this solved the "PRIMARY KEY (credential, group)" issue) to the #JoinColumn annotation inside the #JoinTable of the same property:
#OneToMany
#JoinTable(
name = "credential_groups",
joinColumns = #JoinColumn(name = "group_name"),
inverseJoinColumns = #JoinColumn(name = "username", unique=true)
)
public Set<Credential> getCredentials() {
return credentials;
}
Main take away don't ever ever combine a "mappedBy" with the #JoinTable in a ManyToOne/OneToMany entities pair.

Need help learning how to join on two separate entities using jpa

A Java web project that uses Hibernate just fell into my lap. The original architect has left and I have almost no experience with JPA's Criteria API. So please forgive my ignorance. I'm looking for help with writing several joins using JPA's Criteria API.
I have the following entities and relationships. An ElectronicDevice contains a set of Components which contains a set of Signals.
I'm trying to write a query that returns all of the components and it's signals of a specific electronic device.
I can get the ElectronicDevice with the Component, but I'm unable to get the Signals. I have the following query written.
EntityManager em = getEm();
String electronicDeviceId="Some UUID";
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Component_.class);
Root<ElectronicDevice> i = cq.from(ElectronicDevice.class);
SetJoin<ElectronicDevice, Component> join = i.join(ElectronicDevice_.components);
cq.where(cb.equal(i.get(ElectronicDevice_.id), electronicDeviceId));
cq.multiselect(join);
TypedQuery q = em.createQuery(cq);
List<Component> resultList = q.getResultList();
My Entity Definitions are as follows
#Entity
public class ElectronicDevice implements Serializable {
#Id
#Column(length = 36)
private String id;
#XmlElement
#OneToMany
private Set<Component> components = new HashSet<Component>();
}
#Entity
public class Component extends ParametricObject implements Serializable{
#XmlElement
#OneToMany
private Set<Signal> signals = new HashSet<Signal>();
public Set<Signal> getComponents() {
return signals;
}
}
#Entity
public class Signal implements Serializable {
#Id
#Column(length = 36)
private String id;
}
public class ParametricObject {
#EmbeddedId
#XmlElement
private ParametricId id;
#XmlElement
private String name;
public ParametricId getId() {
return id;
}
public void setId(ParametricId id) {
this.id = id;
}
}
public class ParametricId {
#XmlElement
#ManyToOne
#XmlIDREF
private ElectronicDevice ed;
#XmlAttribute
#XmlID
#Column(length = 36)
private String internalId;
public ElectronicDevice getEd() {
return ed;
}
public void setEd(ElectronicDevice ed) {
this.ed = ed;
}
public String getInternalId() {
return internalId;
}
public void setInternalId(String internalId) {
this.internalId = internalId;
}
}
refer to the discussion at "Hibernate Criteria Join with 3 Tables". your query should looks likes this.
you must add ElectronicDevice with menyToOne annotation in the Component.(see the example at http://www.mkyong.com/hibernate/hibernate-one-to-many-relationship-example-annotation/.)
Criteria c = session.createCriteria(Component.class, "cmp");
c.createAlias("cmp.signals", "signal"); // this will do inner join
c.add(Restrictions.eq("cmp.electronicDevice", "xxxx"));
return c.list();

Categories