Doctrine ORM Conditional Association - doctrine

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);
}

Related

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());

Doctrine Many-to-one multiple target entities

I'm using Doctrine ORM for my project and I have an issue with my tables :
device (id, name, ip_address)
device_group (id, name)
template (id, object_type, object_id)
So these three entities extend from a superclass for common fields such as ID, timestamp...
If we take a look at the template entity, the object_id field refers either to device.id or device_group.id, depending on the value of object_type (a string that can be 'device' or 'group'). I would like to know how to make a relationship that has multiple targets, if possible.
Device_group :
/**
* #ORM\Entity(repositoryClass="App\Doctrine\Repositories\DeviceGroup")
* #ORM\Table(name="device_group")
*/
class DeviceGroup extends BaseModel
{
/**
* #ORM\Column(type="string", name="name", length=255)
*/
protected $name;
}
Device
/**
* #ORM\Entity(repositoryClass="App\Doctrine\Repositories\Device")
* #ORM\Table(name="device")
*/
class Device extends BaseModel
{
/**
* #ORM\Column(type="string", name="name", length=255)
*/
protected $name;
/**
* #ORM\Column(type="string", name="ip_address", length=45)
*/
protected $ip_address;
}
Template
**
* #ORM\Entity(repositoryClass="App\Doctrine\Repositories\Template")
* #ORM\Table(name="template")
*/
class DevicePart extends BaseModel
{
/**
* #ORM\Column(type="string", name="object_type", length=6)
*/
protected $object_type;//can be 'group' or 'device'
/**
* Many DevicePart have one Object
* #ORM\ManyToOne(targetEntity="?????????")
* #ORM\JoinColumn(name="object_id", referencedColumnName="id")
*/
protected $objects;
//target entity can be : App\Doctrine\Entities\Device" or "App\Doctrine\Entities\DeviceGroup
}
I heard about inheritance mapping and I already implement that with my superclass, I looked at some examples but that would make me create new tables, the thing is I have to keep the current structure as the data are used by other programs (SQL Alchemy with Python scripts).
Do you have any idea ?
Thanking you in advance,

Eagerly fetch child when loading parent spring data jpa

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.

Spring MVC + DataTables 1.10 Parameters binding

I'm trying to do a controller that do the Server Side for DataTables.
#RequestMapping(value="/grid", method={RequestMethod.GET}, produces= MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public DataTablesResponse<MyObject> grid(DataTablesRequest dt) {
return service.getListOfMyObjects();
}
DataTablesRequest.class:
public class DataTablesRequest {
private int draw;
private int start;
private int length;
private Search search;
private List<Order> order;
private List<Column> columns;
//... Getters and setters
}
Order.class:
public class Order {
private String column;
private String dir;
//...getters and setters
}
Column.class
public class Column {
private String data;
private String name;
private boolean searchable;
private boolean orderable;
private Search search;
//...Getters and setters
}
Search.class:
public class Search {
private String value;
private boolean regex;
//...getters and setters
}
The problem is that DataTables sends parameters like: column[0][name] and SpringMVC is expecting something like column[0].name.
Is there any way to fix that? How can i bind the parameters of the DataTables to an object?
Kind of a late answer, but maybe this would help someone in the future:
In datatables settings, we need to tell jQuery to not process data and to send JSON:
ajax: {
url: "/some/url.json"),
type: "POST",
data: function (data) {
return JSON.stringify(data);
},
dataType: "json",
processData: false,
contentType: 'application/json;charset=UTF-8'
},
on the Spring side:
#RequestMapping(value = "/some/url")
#ResponseBody
public DataTablesResponse<Element> list(#RequestBody final DataTablesRequest dataTablesRequest) {
// query the DB to get the number of elements (without filtering)
// and the list of elements (filtered and/or paginated)
return new DataTablesResponse(numberOfElements, notifications.getTotalElements(), dataTablesRequest.getDraw(), "", listOfElements);
}
With the following for DataTablesRequest etc (#Data is from lombok, javadoc comments are from the official datatables doc.):
#Data
public class DataTablesRequest {
/**
* Draw counter. This is used by DataTables to ensure that the Ajax returns from server-side processing requests are drawn in sequence by DataTables
* (Ajax requests are asynchronous and thus can return out of sequence). This is used as part of the draw return parameter (see below).
*/
private int draw;
/**
* Paging first record indicator. This is the start point in the current data set (0 index based - i.e. 0 is the first record).
*/
private int start;
/**
* Number of records that the table can display in the current draw. It is expected that the number of records returned will be equal to this number, unless
* the server has fewer records to return. Note that this can be -1 to indicate that all records should be returned (although that negates any benefits of
* server-side processing!)
*/
private int length;
/**
* #see Search
*/
private Search search;
/**
* #see Order
*/
#JsonProperty("order")
private List<Order> orders;
/**
* #see Column
*/
private List<Column> columns;
}
#Data
private static class Search {
/**
* Global search value. To be applied to all columns which have searchable as true.
*/
private String value;
/**
* <code>true</code> if the global filter should be treated as a regular expression for advanced searching, false otherwise. Note that normally server-side
* processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible and at the
* discretion of your script.
*/
private boolean regex;
}
#Data
private static class Order {
/**
* Column to which ordering should be applied. This is an index reference to the columns array of information that is also submitted to the server.
*/
private int column;
/**
* Ordering direction for this column. It will be <code>asc</code> or <code>desc</code> to indicate ascending ordering or descending ordering,
* respectively.
*/
private String dir;
}
#Data
private static class Column {
/**
* Column's data source, as defined by columns.data.
*/
private String data;
/**
* Column's name, as defined by columns.name.
*/
private String name;
/**
* Flag to indicate if this column is searchable (true) or not (false). This is controlled by columns.searchable.
*/
private boolean searchable;
/**
* Flag to indicate if this column is orderable (true) or not (false). This is controlled by columns.orderable.
*/
private boolean orderable;
/**
* Search value to apply to this specific column.
*/
private Search search;
/**
* Flag to indicate if the search term for this column should be treated as regular expression (true) or not (false). As with global search, normally
* server-side processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible
* and at the discretion of your script.
*/
private boolean regex;
}
#Data
public class DataTablesResponse<T> {
/**
* The draw counter that this object is a response to - from the draw parameter sent as part of the data request. Note that it is strongly recommended for
* security reasons that you cast this parameter to an integer, rather than simply echoing back to the client what it sent in the draw parameter, in order
* to prevent Cross Site Scripting (XSS) attacks.
*/
private int draw;
/**
* Total records, before filtering (i.e. the total number of records in the database)
* <p/>
* (NB: I changed this to long)
*/
private long recordsTotal;
/**
* Total records, after filtering (i.e. the total number of records after filtering has been applied - not just the number of records being returned for this
* page of data).
* <p/>
* (NB: I changed this to long)
*/
private long recordsFiltered;
/**
* Optional: If an error occurs during the running of the server-side processing script, you can inform the user of this error by passing back the error message
* to be displayed using this parameter. Do not include if there is no error.
*/
private String error;
/**
* The data to be displayed in the table. This is an array of data source objects, one for each row, which will be used by DataTables. Note that this parameter's
* name can be changed using the ajax option's dataSrc property.
*/
private List<T> data = Lists.newArrayList();
}
How about to implement HandlerMethodArgumentResolver, please refer to https://github.com/rakurakupg/springmvc-jquerydatatables-example
I had similar issue and below worked for me.
Try adding #JsonProperty to each property in every parameter binding class e.g. #JsonProperty(value = "draw"). This way Jackson maps it fine with the JSON sent from DataTable. In my case, I was getting below JSON
{
"draw":1,
"columns":[
{"data":0,"name":"","searchable":true,"orderable":false,"search":{"value":"","regex":false}},
{"data":1,"name":"","searchable":true,"orderable":false,"search":{"value":"","regex":false}}],
"order":[],
"start":0,
"length":-1,
"search":{"value":"","regex":false}
}
Also there is a need to have the empty constructor defined for DataTablesRequest, Column,Order and Search.
If you do not want to have the default constructor, you can add it like below,
public Search(#JsonProperty("value") String value,#JsonProperty("regex") String regex) {
// TODO Auto-generated constructor stub
}
Hope this helps!
The solution to this for me was to send the request as JSON by following the instructions here: ASP.net jQuery DataTables
Essentially just change the JS on your page to something like this:
'ajax': {
'url': 'serverSideTableProviderPage',
'type': 'POST',
'data':function(data)
{
return data = JSON.stringify(data);
}
},

Why does doctrine 2.0 not support default column values?

It appears that Doctrine 2.0 does not support any default values. What's is then the best solution for adding new columns?
Just set default values on your entities:
/** #Entity */
class SomeEntity {
/** #Column(length=50) */
private $someFieldWithDefault = 'defaultvalue';
//...
}

Resources