I have created an entity A with OneToMany relation to B, which have relation OneToMany to C.
I have to clone this A entity and set it in database with new id. Also all deep relations should be cloned with new ids too.
What have I tried is to set A id to null:
$A = clone $A_original;
$A->setId(null);
$em->persist($A);
It creates new record in A table, but does not in B and C.
What should I do to make a full copy of A entity ?
You have to implement a __clone() method in your entities that sets the id to null and clones the relations if desired. Because if you keep the id in the related object it assumes that your new entity A has a relation to the existing entities B and C.
Clone-method for A:
public function __clone() {
if ($this->id) {
$this->setId(null);
$this->B = clone $this->B;
$this->C = clone $this->C;
}
}
Clone-method for B and C:
public function __clone() {
if ($this->id) {
$this->setId(null);
}
}
https://groups.google.com/forum/?fromgroups=#!topic/doctrine-user/Nu2rayrDkgQ
https://doctrine-orm.readthedocs.org/en/latest/cookbook/implementing-wakeup-or-clone.html
Based on the comment of coder4show a clone-method for a OneToMany relationship on A where $this->M is OneToMany and therefore an ArrayCollection:
public function __clone() {
if ($this->id) {
$this->setId(null);
// cloning the relation M which is a OneToMany
$mClone = new ArrayCollection();
foreach ($this->M as $item) {
$itemClone = clone $item;
$itemClone->setA($this);
$mClone->add($itemClone);
}
$this->M = $mClone;
}
}
There is also a module that will do this called DeepCopy:
https://github.com/myclabs/DeepCopy
$deepCopy = new DeepCopy();
$myCopy = $deepCopy->copy($myObject);
You can also add filters to customize the copy process.
I wasnt able to use DeepClone (it require php 7.1+), so I founded more simple way to clone relations in entity __clone method
$this->tags = new ArrayCollection($this->tags->toArray());
A clean way to clone a ArrayCollection:
$this->setItems(
$this->getItems()->map(function (Item $item) {
return clone $item;
})
);
Related
Like in question topic, how can I setup default table prefix in symfony2?
The best if it can be set by default for all entities, but with option to override for individual ones.
Having just figured this out myself, I'd like to shed some light on exactly how to accomplish this.
Symfony 2 & Doctrine 2.1
Note: I use YML for config, so that's what I'll be showing.
Instructions
Open up your bundle's Resources/config/services.yml
Define a table prefix parameter:
Be sure to change mybundle and myprefix_
parameters:
mybundle.db.table_prefix: myprefix_
Add a new service:
services:
mybundle.tblprefix_subscriber:
class: MyBundle\Subscriber\TablePrefixSubscriber
arguments: [%mybundle.db.table_prefix%]
tags:
- { name: doctrine.event_subscriber }
Create MyBundle\Subscriber\TablePrefixSubscriber.php
<?php
namespace MyBundle\Subscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class TablePrefixSubscriber implements \Doctrine\Common\EventSubscriber
{
protected $prefix = '';
public function __construct($prefix)
{
$this->prefix = (string) $prefix;
}
public function getSubscribedEvents()
{
return array('loadClassMetadata');
}
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
$classMetadata = $args->getClassMetadata();
if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
// if we are in an inheritance hierarchy, only apply this once
return;
}
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY
&& array_key_exists('name', $classMetadata->associationMappings[$fieldName]['joinTable']) ) { // Check if "joinTable" exists, it can be null if this field is the reverse side of a ManyToMany relationship
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
}
}
Optional step for postgres users: do something similary for sequences
Enjoy
Alternate answer
This is an update taking into account the newer features available in Doctrine2.
Doctrine2 naming strategy
Doctrine2 uses NamingStrategy classes which implement the conversion from a class name to a table name or from a property name to a column name.
The DefaultNamingStrategy just finds the "short class name" (without its namespace) in order to deduce the table name.
The UnderscoreNamingStrategy does the same thing but it also lowercases and "underscorifies" the "short class name".
Your CustomNamingStrategy class could extend either one of the above (as you see fit) and override the classToTableName and joinTableName methods to allow you to specify how the table name should be constructed (with the use of a prefix).
For example my CustomNamingStrategy class extends the UnderscoreNamingStrategy and finds the bundle name based on the namespacing conventions and uses that as a prefix for all tables.
Symfony2 naming strategy
Using the above in Symfony2 requires declaring your CustomNamingStragery class as a service and then referencing it in your config:
doctrine:
# ...
orm:
# ...
#naming_strategy: doctrine.orm.naming_strategy.underscore
naming_strategy: my_bundle.naming_strategy.prefixed_naming_strategy
Pros and cons
Pros:
running one piece of code to do one single task -- your naming strategy class is called directly and its output is used;
clarity of structure -- you're not using events to run code which alter things that have already been built by other code;
better access to all aspects of the naming conventions;
Cons:
zero access to mapping metadata -- you only have the context that was given to you as parameters (this can also be a good thing because it forces convention rather than exception);
needs doctrine 2.3 (not that much of a con now, it might have been in 2011 when this question was asked :-));
Simshaun's answer works fine, but has a problem when you have a single_table inheritance, with associations on the child entity. The first if-statement returns when the entity is not the rootEntity, while this entity might still have associations that have to be prefixed.
I fixed this by adjusting the subscriber to the following:
<?php
namespace MyBundle\Subscriber;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
class TablePrefixSubscriber implements EventSubscriber
{
protected $prefix = '';
/**
* Constructor
*
* #param string $prefix
*/
public function __construct($prefix)
{
$this->prefix = (string) $prefix;
}
/**
* Get subscribed events
*
* #return array
*/
public function getSubscribedEvents()
{
return array('loadClassMetadata');
}
/**
* Load class meta data event
*
* #param LoadClassMetadataEventArgs $args
*
* #return void
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
$classMetadata = $args->getClassMetadata();
// Only add the prefixes to our own entities.
if (FALSE !== strpos($classMetadata->namespace, 'Some\Namespace\Part')) {
// Do not re-apply the prefix when the table is already prefixed
if (false === strpos($classMetadata->getTableName(), $this->prefix)) {
$tableName = $this->prefix . $classMetadata->getTableName();
$classMetadata->setPrimaryTable(['name' => $tableName]);
}
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide'] == true) {
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
// Do not re-apply the prefix when the association is already prefixed
if (false !== strpos($mappedTableName, $this->prefix)) {
continue;
}
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
}
}
}
This has a drawback though;
A not wisely chosen prefix might cause conflicts when it's actually already part of a table name.
E.g. using prefix 'co' when theres a table called 'content' will result in a non-prefixed table, so using an underscore like 'co_' will reduce this risk.
Also, you can use this bundle for the new version of Symfony (4) - DoctrinePrefixBundle
I don't when to implement a solution that involved catching event (performance concern), so I have tried the Alternate Solution but it doesn't work for me.
I was adding the JMSPaymentCoreBundle and wanted to add a prefix on the payment tables.
In this bundle, the definition of the tables are in the Resources\config\doctrine directory (xml format).
I have finally found this solution:
1) copy doctrine directory containing the definitions on the table and paste it in my main bundle
2) modify the name of the tables in the definitions to add your prefix
3) declare it in your config.yml, in the doctrine/orm/entity manager/mapping section (the dir is the directory where you have put the modified definitions):
doctrine:
orm:
...
entity_managers:
default:
mappings:
...
JMSPaymentCoreBundle:
mapping: true
type: xml
dir: "%kernel.root_dir%/Resources/JMSPayment/doctrine"
alias: ~
prefix: JMS\Payment\CoreBundle\Entity
is_bundle: false
tested with Symfony 6 :
Create a class that extends Doctrine's UnderscoreNamingStrategy and handles the prefix :
<?php
# src/Doctrine/PrefixedNamingStrategy.php
namespace App\Doctrine;
use Doctrine\ORM\Mapping\UnderscoreNamingStrategy;
class PrefixedNamingStrategy extends UnderscoreNamingStrategy
{
private const PREFIX = 'sf';
public function classToTableName($className)
{
$underscoreTableName = parent::classToTableName($className);
return self::PREFIX . '_' . $underscoreTableName;
}
}
and configure doctrine to use it :
# config/packages/doctrine.yaml
doctrine:
orm:
naming_strategy: 'App\Doctrine\PrefixedNamingStrategy'
#simshaun answer is good, but there is a problem with Many-to-Many relationships and inheritance.
If you have a parent class User and a child class Employee, and the Employee own a Many-to-Many field $addresses, this field's table will not have a prefix.
That is because of:
if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
// if we are in an inheritance hierarchy, only apply this once
return;
}
User class (parent)
namespace FooBundle\Bar\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* #ORM\Entity()
* #ORM\Table(name="user")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({"user" = "User", "employee" = "\FooBundle\Bar\Entity\Employee"})
*/
class User extends User {
}
Employee class (child)
namespace FooBundle\Bar\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* #ORM\Entity()
*/
class Employee extends FooBundle\Bar\Entity\User {
/**
* #var ArrayCollection $addresses
*
* #ORM\ManyToMany(targetEntity="\FooBundle\Bar\Entity\Adress")
* #ORM\JoinTable(name="employee_address",
* joinColumns={#ORM\JoinColumn(name="employee_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="address_id", referencedColumnName="id")}
* )
*/
private $addresses;
}
Address class (relation with Employee)
namespace FooBundle\Bar\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* #ORM\Entity()
* #ORM\Table(name="address")
*/
class Address {
}
With the original solution, if you apply pref_ prefixe to this mapping, you will end up with tables :
pref_user
pref_address
employee_address
Solution
A solution can be to modify, in the answer of #simshaun, the point 4 like this:
Create MyBundle\Subscriber\TablePrefixSubscriber.php
<?php
namespace MyBundle\Subscriber;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class TablePrefixSubscriber implements \Doctrine\Common\EventSubscriber
{
protected $prefix = '';
public function __construct($prefix)
{
$this->prefix = (string) $prefix;
}
public function getSubscribedEvents()
{
return array('loadClassMetadata');
}
public function loadClassMetadata(LoadClassMetadataEventArgs $args)
{
$classMetadata = $args->getClassMetadata();
// Put the Many-yo-Many verification before the "inheritance" verification. Else fields of the child entity are not taken into account
foreach($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY
&& array_key_exists('name', $classMetadata->associationMappings[$fieldName]['joinTable']) // Check if "joinTable" exists, it can be null if this field is the reverse side of a ManyToMany relationship
&& $mapping['sourceEntity'] == $classMetadata->getName() // If this is not the root entity of an inheritance mapping, but the "child" entity is owning the field, prefix the table.
) {
$mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
if($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) {
// if we are in an inheritance hierarchy, only apply this once
return;
}
$classMetadata->setTableName($this->prefix . $classMetadata->getTableName());
}
}
Here we handle the Many-to-Many relationship before verifying if the class is the child of an inheritance, and we add $mapping['sourceEntity'] == $classMetadata->getName() to add the prefix only one time, on the owning entity of the field.
I have three objects:
class Customer: Object {
dynamic var solution: Solution!;
...
}
class Solution: Object {
dynamic var data: Data!;
...
}
class Data: Object {
...
}
Now i need to move the Data Object from Solution to Customer so that it becomes:
class Customer: Object {
dynamic var solution: Solution!;
dynamic var data: Data!;
...
}
I have no idea how I have to implement my Realm Migration method so that everything works fine and that I wont lose data.
I did some experiments with the Realm migrations sample app and came up with this potential solution:
In a migration block, you can only interact with your Realm file via the migration object. Any attempts to directly access the Realm file mid-migration will result in an exception.
That being said, it's possible to have nested calls to migration.enumerateObjects referencing different Realm model object classes. As such, it should simply be a matter of initially enumerating through the Customer objects, and in each iteration, enumerate through the Solution objects to find the corresponding one with the right data value. Once found, it should be possible to set the Customer object with the data from the Solution object.
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
migration.enumerateObjects(ofType: Customer.className()) { oldCustomerObject, newCustomerObject in
migration.enumerateObjects(ofType: Solution.className()) { oldSolutionObject, newSolutionObject in
//Check that the solution object is the one referenced by the customer
guard oldCustomerObject["solution"].isEqual(oldSolutionObject) else { return }
//Copy the data
newCustomerObject["data"] = oldSolutionObject["data"]
}
}
}
}
})
I feel I need to stress that this code is by no means tested and guaranteed to work in its present state. So I recommend you make sure you thoroughly test it on some dummy data you wouldn't miss beforehand. :)
Swift 4, Realm 3
I had to migrate a Realm object that linked to another object. I wanted to remove the explicit link and replace it with an object ID. TiM's solution got me most of the way there, and just needed a little refinement.
var config = Realm.Configuration()
config.migrationBlock = { migration, oldSchemaVersion in
if oldSchemaVersion < CURRENT_SCHEMA_VERSION {
// enumerate the first object type
migration.enumerateObjects(ofType: Message.className()) { (oldMsg, newMsg) in
// extract the linked object and cast from Any to DynamicObject
if let msgAcct = oldMsg?["account"] as? DynamicObject {
// enumerate the 2nd object type
migration.enumerateObjects(ofType: Account.className()) { (oldAcct, newAcct) in
if let oldAcct = oldAcct {
// compare the extracted object to the enumerated object
if msgAcct.isEqual(oldAcct) {
// success!
newMsg?["accountId"] = oldAcct["accountId"]
}
}
}
}
}
}
I'm using JPA not Hibernate. I have a Problem with my Relationship as follows. I have a 1:N Relationship (Parent:Child).
On a JSP-Site, the Parent and the Children are displayed.
If I load the JSP-Site (for the first time) and save a Parent with a Child, all is ok. The Parent is saved and the Child is saved too to the DB with the foreign key.
After that, a second JSP-Site is displayed with the Parent and Children.
But if I go back to the first Site and save a second Child (adding Child) to existing Parent, the childEntityList is still the old one, I mean, the new Child is not inside this List, but inserted in the database.
Why the childEntityList is old and doesn't update?
The Parent:
#Entity
#Table(schema = "test", name = "Parent")
#XmlRootElement
#NamedQueries({...})
public class Parent implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "A_SEQ")
#Basic(optional = false)
#NotNull
#Column(name = "ID")
private int id;
#OneToMany(mappedBy = "p", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<ChildEntity> childEntityList = new ArrayList<ChildEntity>();
// ...
public List<ChildEntity> getChildEntityList() {
return childEntityList;
}
public void setChildEntityList(List<ChildEntity> childEntityList) {
this.childEntityList = childEntityList;
}
public void addChildEntityToParent(ChildEntity c) {
this.childEntityList.add(c);
if(c.getParent() != this) {
c.setParent(this);
}
}
public void removeChildEntityFromParent(ChildEntity c) {
childEntityList.remove(c);
}
// ...
}
The Child:
#Entity
#Table( name = "Child" )
#NamedQueries({...})
public class ChildEntity implements Serializable {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY )
#Column( name = "CHILD_ID" )
private Long childId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
private Parent p;
// ...
public Parent getParent() {
return p;
}
public void setParent(Parent p) {
this.p= p;
if(!p.getChildEntityList().contains(this)) {
p.getChildEntityList().add(this);
}
}
Save-Code:
Parent p = new Parent(...);
ChildEntity c = new ChildEntity(...);
p.addChildEntityToParent(c);
childEntityFacade.create(c);
View-Code:
// ...
public doSomething(Parent p) {
super(p);
}
// ...
List<ChildEntity> cList = p.getChildEntityList();
System.out.print("Size: " + c.size());
// ...
In the View-Code, the size of the childEntityList is every time the same even I add a Child.
Only when I restart the server, all Children of the Parent are displayed.
When is the childEntityList in the Parent Class filled?
Is it automatically, because of the #OneToMany?
Can someone explain it?
Update:
Should I merge the EntityManager every time when I add/remove a Child object from the Parent list or have I do some other things?
You need to maintain both sides of a bi-directional relationship. See here.
(I'm using JPA not Hibernate!)
I guess you meant that you are not using the Hibernate API.
JPA is only a persistence API specification and, by itself, does nothing.
So, if you're using JPA... you're using something to do the persistence.
JPA delegates the persistence work to a persistence engine such as Hibernate, EclipseLink, OpenJPA or something else.
You can configure your app to use JPA interfaces with a specific persistence provider, or if the app is running in a JEE container, then the JPA implementation should be provided for you: GlassFish (uses OpenJPA), WebLogic (uses EclipseLink), TomEE (uses OpenJPA).
Should i merge the EntityManager every time when i add/remove a Child
Object from the Parent List...
You don't merge the EntityManager; you use it to merge the parent entity:
parent = em.merge(parent);
Relating to your question, when you use merge() in this way... (and because the mapping specifies CascadeType.ALL) then upon reloading page ("Site 1") you should get the refreshed parent object with an updated object network containing changes you made prior to merge(parent), including any newly added children.
Yes, you need to merge parent entity. I was having same issue and it got resolved once I merged parent entity.
Also you need to maintain both sides of the relationship.
In Symfony 2 I generate a Bundle for storing any type of document into database, but I need the BLOB column type.
Tnx to this question I add the class BlobType into Doctrine DBAL, but for use the new column type I had to change
Doctrine\DBAL\Types\Type
[...]
const BLOB = 'blob';
[...]
private static $_typesMap = array(
[...],
self::BLOB => 'Doctrine\DBAL\Types\BlobType',
);
Doctrine\DBAL\Platforms\MySqlPlatform (maybe it was better if I had changed Doctrine\DBAL\Platforms\AbstractPlatform)
[...]
protected function initializeDoctrineTypeMappings()
{
$this->doctrineTypeMapping = array(
[...],
'blob' => 'blob',
);
}
[...]
/**
* Obtain DBMS specific SQL to be used to create time fields in statements
* like CREATE TABLE.
*
* #param array $fieldDeclaration
* #return string
*/
public function getBlobTypeDeclarationSQL(array $fieldDeclaration)
{
return 'BLOB';
}
Now I don't have mouch time for a 'pretty solution', but in future I would like to restore the Doctrine classes and be able to assign the new column type into Symfony 2 bootstrap.
I think I should edit my app/bootstrap.php.cache but I don't have idea how to intervene.
this worked for me:
create your blobtype (See https://gist.github.com/525030/38a0dd6a70e58f39e964ec53c746457dd37a5f58)
add this to your Bundle initialization (/src/YOURDOMAIN/YOURBUNDLE/YOURDOMAINYOUBUNDLE.php)
class YourBundle extends Bundle
{
public function boot()
{
$em = $this->container->get('doctrine.orm.entity_manager');
Type::addType('blob', 'YOURDOMAIN\YOURBUNDLE\YOURTYPEDIRECTORY\BlobType');
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('blob','blob');
}
}
Small improvement for registration blob type in XXXBundle::boot(), but can be necessary during unittests.
class XXXBundle extends Bundle
{
public function boot()
{
// Add blob type
if(!Type::hasType('blob')) {
Type::addType('blob', '{CLASS_PATH}\\Blob');
}
// Add blob type to current connection.
// Notice: during tests there can be multiple connections to db so
// it will be needed to add 'blob' to all new connections if not defined.
$em = $this->container->get('doctrine.orm.entity_manager');
if (!$em->getConnection()->getDatabasePlatform()->hasDoctrineTypeMappingFor('blob')) {
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('blob','blob');
}
}
I just found this gist:
https://gist.github.com/525030/38a0dd6a70e58f39e964ec53c746457dd37a5f58
app/bootstrap.php:
<?php
// ...
$em = Doctrine\ORM\EntityManager::create($conn, $config, $evm);
// types registration
Doctrine\DBAL\Types\Type::addType('blob', 'Doctrine\DBAL\Types\Blob');
$em->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('BLOB', 'blob');
BTW bootstrap.cache.php is auto-generated AFAIK.. So changes there would be overwritten.
suppose i have the following entity domain:
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE")
public abstract class Entity1 {
//some attributes
}
#Entity
#DiscriminatorValue("T1")
public class Entity2 extends Entity1 {
#OneToMany(fetch=FetchType.EAGER, cascade = { CascadeType.ALL }, mappedBy="parent")
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Entity1Detail> details = new HashSet<Entity1Detail>();
}
#Entity
public class Entity1Detail {
#ManyToOne
#JoinColumn(name="REF")
private Entity2 parent;
#Basic
private Integer quantity;
}
#Entity
#DiscriminatorValue("T2")
public class Entity3 extends Entity1 {
//some other attributes
}
when i do a JPQL query:
select e from Entity1 e left join e.details d where d.quantity > 1
it runs well (left join ;P). however when i try to construct the same query using JPA2 Criteria API:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery q = builder.createQuery();
Root r = q.from(Entity1.class);
q.select(r);
q.where(builder.gt(r.join("details", JoinType.LEFT).get("quantity"), 1));
i get NPE in "join" because the attribute "details" doesn't belong to Entity1 (which is actually true, i have to select on Entity2.class instead). the thing is that when i have to construct my dynamic query using Criteria API i don't really know anything about hierarchy, i'm just passed a Class.
i understand that Criteria API is typesafe and all that, but is there a way to work around this? with aliases maybe (as before i used Hibernate Criteria API, traversing joins with aliases):
Criteria c = session.createCriteria(Entity1.class);
c.createAlias("details", "d");
c.add(Restrictions.ge("d.quantity", 1));
You need to base your query on entity2.details. Since the criteria API is type-safe, it catches that entity1 has no field named "details"
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery q = builder.createQuery();
Root r = q.from(Entity2.class); // Must use subclass as root
q.select(r);
q.where(builder.gt(r.join("details", JoinType.LEFT).get("quantity"), 1));
Since Entity2 extends Entity1, you can cast your results as the parent type safely. For example:
CriteriaQuery<Entity1> q = builder.createQuery(Entity1.class);
Root r = q.from(Entity2.class); // Must use subclass as root
will return a list of Entity1
CriteriaQuery<Entity2> q = builder.createQuery(Entity2.class);
Root r = q.from(Entity2.class); // Must use subclass as root
will return a list of Entity2
EDIT:
I think I misunderstood the goal here. If you want all Entity1 UNLESS they are Entity2 with details.quantity <= 1, you need to do more.
You can't use a left join from Entity1Detail to Entity1, because that is not strictly type safe. Instead, you need to join Entity2 to Entity1Detail somehow. Probably the best tool to use here is a correlated subquery.
CriteriaQuery<Entity1> q = builder.createQuery(Entity1.class);
Root<Entity1> ent1 = q.from(Entity1.class);
SubQuery<Entity2> subq = q.subquery(Entity2.class);
Root<Entity2> ent2 = subq.from(Entity2.class);
Path<Integer> quantity = ent2.join("details", JoinType.LEFT).get("quantity");
Predicate lessThan = builder.lte(quantity,1);
Predicate correlatedSubqJoin = cb.equal(ent1,ent2)
subq.where(lessThan, correlatedSubqJoin);
q.select(ent1);
q.where(builder.exists(subq).not());
The criteria API does not know that you are single table inheritance, so you have to write your queries for all inheritance strategies, including a Joined inheritance strategy.