Eagerly fetch child when loading parent spring data jpa - spring

Link.java
#Entity
#Table(name = "LINK")
#AttributeOverride(name="id", column=#Column(name="LINK_ID"))
public class Link extends AbstractAuditableEntity<Integer> {
/**
*
*/
private static final long serialVersionUID = 3825555385014396995L;
#Column(name="NAME")
private String name;
#Column(name="UI_SREF")
private String uiSref;
#ManyToOne
#JoinColumn(name="PARENT_LINK_ID")
private Link parentLink;
#OneToMany(mappedBy="parentLink", fetch = FetchType.EAGER)
private List<Link> childLinks;
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* #return the uiSref
*/
public String getUiSref() {
return uiSref;
}
/**
* #param uiSref the uiSref to set
*/
public void setUiSref(String uiSref) {
this.uiSref = uiSref;
}
/**
* #return the parentLink
*/
public Link getParentLink() {
return parentLink;
}
/**
* #param parentLink the parentLink to set
*/
public void setParentLink(Link parentLink) {
this.parentLink = parentLink;
}
/**
* #return the childLinks
*/
public List<Link> getChildLinks() {
return childLinks;
}
/**
* #param childLinks the childLinks to set
*/
public void setChildLinks(List<Link> childLinks) {
this.childLinks = childLinks;
}
}
LinkRepository .java
public interface LinkRepository extends BaseRepository<Integer, Link> {
#Query("select distinct p from Link l JOIN fetch l.parentLink p where l.id in (select lar.link.id from LinkAccessRole lar where lar.accessRoleLu in ?1) and p.id in (select lar.link.id from LinkAccessRole lar where lar.accessRoleLu in ?1)")
public List<Link> getNavigationByaccessRoleLuList(List<AccessRoleLu> accessRoleLu);
}
Link_Table
Link_Access_Role Table
generated Queries:
SELECT DISTINCT t0.LINK_ID, t0.CREATED_BY_ID, t0.CREATED_DATE, t0.LAST_MODIFIED_BY_ID, t0.LAST_MODIFIED_DATE, t0.NAME, t0.UI_SREF, t0.PARENT_LINK_ID FROM LINK t0, LINK t1 WHERE ((t1.LINK_ID IN (SELECT t2.LINK_ID FROM LINK_ACCESS_ROLE t3, LINK t2 WHERE ((t3.ACCESS_ROLE_ID IN (?,?)) AND (t2.LINK_ID = t3.LINK_ID))) AND t0.LINK_ID IN (SELECT t4.LINK_ID FROM LINK_ACCESS_ROLE t5, LINK t4 WHERE ((t5.ACCESS_ROLE_ID IN (?,?)) AND (t4.LINK_ID = t5.LINK_ID)))) AND (t0.LINK_ID = t1.PARENT_LINK_ID))
bind => [4 parameters bound]
SELECT LINK_ID, CREATED_BY_ID, CREATED_DATE, LAST_MODIFIED_BY_ID, LAST_MODIFIED_DATE, NAME, UI_SREF, PARENT_LINK_ID FROM LINK WHERE (PARENT_LINK_ID = ?)
bind => [1 parameter bound]
SELECT LINK_ID, CREATED_BY_ID, CREATED_DATE, LAST_MODIFIED_BY_ID, LAST_MODIFIED_DATE, NAME, UI_SREF, PARENT_LINK_ID FROM LINK WHERE (PARENT_LINK_ID = ?)
bind => [1 parameter bound]
I get one query for each child related to the fetched parent Regardless it has the access role or not.
i want to fetch the parents and its childs that have access role not all childs that related to that parent.

The only way that you can fetch a parent entity and have one of its collections populated with a subset of entries based on some criteria is by using Hibernate's proprietary filters.
I'm not certain whether the other JPA providers provide some proprietary solution either, but JPA itself doesn't offer this directly.
You first need to register a filter definition using #FilterDef and then you need to reference the filter's definition using the #Filter on your collection property.
The hard part here is that you can't rely on Spring data's #Query or their repository implementation generation process to help. You will need to use a real implementation so that you can manually enable this hibernate filter before you query the parent entity.
Filter filter = session.enableFilter( "link-with-restrictions-by-roles" );
filter.setParameter( "roles", yourRolesList );
return session.createQuery( ... ).getResultList();
The documentation describes the use of #Filter and #FilterDef in detail. You can also find another post of mine where I give slightly more implementation details here.

Related

JaVers - retrieve audit changes by composite key

I have a test case which tries to query Child entity changes by its instanceId, it throws an Exception:
#TypeName("EntityOne")
class EntityOne {
#Id int id
String name
List<EntityTwo> entityTwos
EntityOne(int id, String name, List<EntityTwo> entityTwos) {
this.id = id
this.name = name
this.entityTwos = entityTwos
}
}
#TypeName("EntityTwo")
class EntityTwo {
#Id int id
String name
#Id int entityOneId
EntityTwo(int id, String name, entityOneId) {
this.id = id
this.name = name
this.entityOneId = entityOneId
}
}
These are the data audited
oldOne = new EntityOne(1, "EntityOne", [new EntityTwo(1, "EntityTwo",1)])
newOne = new EntityOne(1, "EntityOne", [new EntityTwo(1, "EntityTwo",1),
new EntityTwo(2, "EntityTwoOne",1)])
This is query throwing exception
entityTwoChanges = javers.findChanges(QueryBuilder.byInstanceId(1, EntityTwo) // Error is thrown
.withNewObjectChanges()
.withChildValueObjects()
.build())
Exception:
java.lang.Integer cannot be cast to java.util.Map
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.util.Map
at org.javers.core.metamodel.type.InstanceIdFactory.dehydratedLocalId(InstanceIdFactory.java:48)
at org.javers.core.metamodel.type.InstanceIdFactory.create(InstanceIdFactory.java:22)
at org.javers.core.metamodel.type.EntityType.createIdFromInstanceId(EntityType.java:127)
at org.javers.core.metamodel.object.GlobalIdFactory.createInstanceId(GlobalIdFactory.java:115)
at org.javers.core.metamodel.object.GlobalIdFactory.createFromDto(GlobalIdFactory.java:127)
at org.javers.repository.jql.FilterDefinition$IdFilterDefinition.compile(FilterDefinition.java:27)
at org.javers.repository.jql.JqlQuery.compile(JqlQuery.java:120)
at org.javers.repository.jql.QueryCompiler.compile(QueryCompiler.java:16)
at org.javers.repository.jql.ChangesQueryRunner.queryForChanges(ChangesQueryRunner.java:20)
at org.javers.repository.jql.QueryRunner.queryForChanges(QueryRunner.java:48)
at org.javers.core.JaversCore.findChanges(JaversCore.java:196)
at com.example.CaseQueryByCompositeKey.should able to query audit changes by composite key(CaseQueryByCompositeKey.groovy:60)
and also Is there way to query by composite key in JaVers?
It worked after passing instanceId as map:
entityTwoChanges = javers.findChanges(QueryBuilder.byInstanceId([id: 1, entityOneId: 1], EntityTwo)
.withNewObjectChanges()
.withChildValueObjects()
.build())
It's explicitly shown in the javadoc
/**
* Query for selecting Changes, Snapshots or Shadows for a given Entity instance.
* <br/><br/>
*
* For example, last Changes on "bob" Person:
* <pre>
* javers.findChanges( QueryBuilder.byInstanceId("bob", Person.class).build() );
* </pre>
*
* #param localId Value of an Id-property. When an Entity has Composite-Id (more than one Id-property) —
* <code>localId</code> should be <code>Map<String, Object></code> with
* Id-property name to value pairs.
* #see CompositeIdExample.groovy
*/
public static QueryBuilder byInstanceId(Object localId, Class entityClass){
Validate.argumentsAreNotNull(localId, entityClass);
return new QueryBuilder(new IdFilterDefinition(instanceId(localId, entityClass)));
}
try to read the javadoc first, before asking questins about a method

How to give ttl in Cassandra when inserting data in batches?

Hello I am using Cassandra to save user data . I want to store data of a user for only 24 hours so I am giving a ttl for 24 hours. For each user there are multiple entries. So I want to batch insert data for each user instead of multiple calls to data base . I am using Cassandra operations to give ttl . I am able to give ttl for single record . How to provide ttl when inserting data in batches
public class CustomizedUserFeedRepositoryImpl<T> implements CustomizedUserFeedRepository<T> {
private CassandraOperations cassandraOperations;
#Autowired
CustomizedUserFeedRepositoryImpl(CassandraOperations cassandraOperations){
this.cassandraOperations = cassandraOperations;
}
#Override
public <S extends T> S save(S entity, int ttl){
InsertOptions insertOptions;
if(ttl == 0) {
insertOptions = InsertOptions.builder().ttl(Duration.ofHours(24)).build();
} else {
insertOptions = InsertOptions.builder().ttl(ttl).build();
}
cassandraOperations.insert(entity,insertOptions);
return entity;
}
#Override
public void saveAllWithTtl(java.lang.Iterable<T> entities, int ttl){
entities.forEach(entity->{
save(entity,ttl);
});
}
}
As you can see I have to iterate over the list make and make database calls for each record . The batch operation cassandraOperations.batchOps().insert() only takes list of objects . How to set ttl for each record when using batchops() fucntion ?
/**
* Add a collection of inserts with given {#link WriteOptions} to the batch.
*
* #param entities the entities to insert; must not be {#literal null}.
* #param options the WriteOptions to apply; must not be {#literal null}.
* #return {#code this} {#link CassandraBatchOperations}.
* #throws IllegalStateException if the batch was already executed.
* #since 2.0
*/
CassandraBatchOperations insert(Iterable<?> entities, WriteOptions options);
You can use insert(Iterable<?> entities, WriteOptions options) method
#EqualsAndHashCode(callSuper = true)
public class WriteOptions extends QueryOptions {
private static final WriteOptions EMPTY = new WriteOptionsBuilder().build();
private final Duration ttl;
private final #Nullable Long timestamp;
batchOperations.insert(entity, WriteOptions.builder().ttl(20).build());

Java, Hibernate -- Can't retrieve id after persisting entity with separate embedded key class

I am working on a sample Springboot server application and using hibernate for JPA. I am using a generic repository pattern that performs all the CRUD operations on my entity. I am following this example :
http://www.concretepage.com/spring-boot/spring-boot-rest-jpa-hibernate-mysql-example that I came across. (My idea to have a Generic repository was to have a similar implementation for all CRUD operations, than explicitly stating one in each Service/DAO or repository implementation for each Entity) In the above example the #ID attribute is in the same class as the Entity. As a result of that I was able to persist an entity and the id would be reflected in the object after entityManager.persist(object)
In my code I have the Key class separate and it is referenced in the Entity class. On calling persist on EntityManager, a row is created in the database (since the column for the primary key is set to auto-increment in the database), but that same ID isn't reflected in the object after calling persist(). At all times my ID attribute within the key class is set to 0 that is the default int value. I would like to know if there is a way that I could fetch the ID of the inserted object either through Session or EntityManager. Also is there any alternate strategy to going about this problem without having the include the primary key in the Entity class itself. (As of now, I have looked at multiple posts on SO but haven't been able to get to a solution to my problem.)
Entity class
#Entity
#Table(name = "articles")
public class SampleArticle extends AbstractDomainObject {
/** The serialVersionUID. */
private static final long serialVersionUID = 7072648542528280535L;
/** Uniquely identifies the article. */
#EmbeddedId
#AttributeOverride(name = "articleId", column = #Column(name = "article_id"))
#GeneratedValue(strategy = GenerationType.IDENTITY)
//#GeneratedValue(strategy = GenerationType.AUTO)
private SampleArticleKey key;
/** Indicates the title. */
#Column(name = "title")
private String title;
Key class
#Embeddable
public class SampleArticleKey extends AbstractDomainKey {
/**
* Serial version id.
*/
private static final long serialVersionUID = 1325990987094850016L;
/** The article id. */
private int articleId;
Repository class
#Repository
#Transactional
public class SampleArticleRepository extends
AbstractRepository<SampleArticle, SampleArticleKey> implements
ISampleArticleRepository<SampleArticle, SampleArticleKey> {
/*
* (non-Javadoc)
* #see
* com.wpi.server.entity.repository.ISampleArticleRepository#addArticle
* (java.lang.Object)
*/
#Override
public SampleArticle create(SampleArticle article) throws Exception {
return super.create(article);
}
Abstract Repository
#Transactional
public abstract class AbstractRepository<T extends AbstractDomainObject, K
extends AbstractDomainKey> {
/** The entity manager. */
#PersistenceContext
private EntityManager entityManager;
/** The Constant LOGGER. */
private static final Logger LOGGER = Logger.getLogger(AbstractRepository.class.getName());
/**
* Persist the given object at persistence storage.
*
* #param object
* The object extending {#link AbstractDomainObject} which needs
* to be inserted.
* #return object of type {#link AbstractDomainObject} with the newly
* generated id.
* #throws Exception
* If unable to insert data.
*/
public T create(T object) throws Exception {
final Session session = entityManager.unwrap(Session.class);
session.getTransaction().begin();
session.save(object);
session.flush();
session.getTransaction().commit();
session.close();
LOGGER.fine("Entity CREATED successfully.");
return object;
};
Let me give you a working embeddable key example. It might help.
First overwrite equals() and hashCode() methods so that Hibernate proper identifies objects in the cash.
Now you can persist objects
Let me know if this helps or you have other issues with this.

Spring Jdbc mapping rows

How can i map rows of two tables that refers each other?
For example there are Employee and Department tables. Employee has a reference to department model which is the department of the employee and Department has a reference to employee model which is the manager of the department. So how can i do map rows using spring RowMapper.
Thanks,
How can i map rows of two tables that refers each other?
for example like this:
public class TwoTablesRowMapper implements RowMapper<Map<String, Object>> {
/**
* Map data from select over 2 tables e.g.:
*
* select
* A.foo as afoo,
* B.bar as bbar
* from PARENT A,
* CHILD B
* where A.ID = B.ID
*
*
* #param rs
* #param rowNum
* #return
* #throws SQLException
*/
public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException {
Map<String, Object> resultMap = new HashMap<String, Object>();
// instead of a map one could fill an object
// e.g.: myObject.set.afoo(afoo)
resultMap.put("afoo", rs.getString("afoo"));
resultMap.put("bbar", rs.getString("bbar"));
return resultMap;
}
}
for the SQL part i recommend you create a new question with specific SQL details (tables, relations, etc.) and tagged sql, it should find more (sql-savvy) viewers this way

Doctrine ORM Conditional Association

i'm building a Q&A site and my questions, answers and comments are on the same posts table. But their postType is different. I can get answers for a question and comments for an answer with this association:
/**
* #OneToMany(targetEntity="Cms\Entity\Post", mappedBy="parent")
*/
private $answers;
/**
* #OneToMany(targetEntity="Cms\Entity\Post", mappedBy="parent")
*/
private $comments;
But i think this is not the correct way to do this because if i fetch a question both answers and comments are filling with just answers. I have to set a condition for relation like postType = 1
How can i do this?
Your schema is invalid. You schould have two different objects for answers and comments as they are two different things, even if they share a common interface.
You should create two entities, Answer and Comment and create assocations to them. As they are almost the same thing you could create an abstract class, AbstractContent, that defines all required fields and accessor methods. Doctrine supports inheritance so the final database schema will be exactly the same, but your OO model will be correct.
/**
* #MappedSuperclass
* #InheritanceType("SINGLE_TABLE")
* #DiscriminatorColumn(type = "string", name = "discriminator")
* #DiscriminatorMap({ "answer" = "Answer", "comment" = "Comment" })
*/
abstract class AbstractContent {
/** #Column(type = "integer") #Id #GeneratedValue("AUTO") */
protected $id;
/** #Column(type="text") */
protected $content;
/** #Column(type = "datetime", name = "created_at") */
protected $createdAt;
public function __construct() {
$this->createdAt = new \DateTime();
}
}
/** #Entity */
class Answer extends AbstractContent { }
/** #Entity */
class Comment extends AbstractContent { }
/**
* #OneToMany(targetEntity="Cms\Entity\Answer", mappedBy="parent")
*/
private $answers;
/**
* #OneToMany(targetEntity="Cms\Entity\Comment", mappedBy="parent")
*/
private $comments;
You can read more about inheritance in Doctrine on its documentation pages: Inheritance Mapping
Use Doctrine's Filtering Collections Criteria class. You can even filter the collection first before the sql query:
If the collection has not been loaded from the database yet, the
filtering API can work on the SQL level to make optimized access to
large collections.
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
...
/** #var Collection */
protected $posts;
/**
* #return Post[]
*/
public function getAnswers()
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq('postType', 'answer'))
;
return $this->posts->matching($criteria);
}
/**
* #return Post[]
*/
public function getComments()
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq('postType', 'comment'))
;
return $this->posts->matching($criteria);
}

Resources