QueryDSL joins and subqueries with native SQL - java

I use Spring Data and QueryDSL 3.2.4 and I want to implement the following SQL query with QueryDSLs native SQL API:
WITH ORDER_GROUP AS ( -- TODO have to merge this subquery into the main query
SELECT
ordergroup
,count(ID) AS nofOrdersPerGroup
,MIN(priority) as minPriority
,MIN(requesteddeliverytime) as minRequestedDeliveryTime
,MIN(creationtime) as minCreationTime
FROM ORDERHEADER hh
group by orderGroup
),
ALL_ORDERS AS ( -- TODO have to merge this subquery into the main query
SELECT h.ordercode
, h.ordergroup
, h.priority
, h.requesteddeliverytime
, h.creationtime
, h.statecode
, (SELECT COUNT(ID)
FROM orderposition p
WHERE p.orderheaderid = h.ID
) AS nof_positions_per_order
, CASE
WHEN h.ordergroup IS NOT NULL
THEN g.nofOrdersPerGroup
ELSE 1
END AS nof_orders_per_group
, CASE
WHEN h.ordergroup IS NOT NULL
THEN g.minPriority
ELSE h.priority
END AS most_important_prio
, CASE
WHEN h.ordergroup IS NOT NULL
THEN g.minRequestedDeliveryTime
ELSE h.requesteddeliverytime
END AS earliest_del_time
, CASE
WHEN h.ordergroup IS NOT NULL
THEN g.minCreationTime
ELSE h.creationtime
END AS earliest_cre_time
FROM ORDERHEADER h left outer join ORDER_GROUP g on h.ordergroup = g.ordergroup
WHERE 1=1 -- TODO have to add filter clauses here
)
SELECT ordercode
, ordergroup
, priority
, requesteddeliverytime
, creationtime
, statecode
, nof_positions_per_order
, nof_orders_per_group
, most_important_prio
, earliest_del_time
, earliest_cre_time
FROM ALL_ORDERS
ORDER BY most_important_prio
, earliest_del_time
, earliest_cre_time
, priority
, requesteddeliverytime
, creationtime;
The join is not on a FK column but on some arbitrary attribute of the subquery ORDER_GROUP. This subquery aggregates some min / max valuse on on OrderHeader for later use in sorting.
The query types are:
package com.stoecklin.wms.entity;
import static com.mysema.query.types.PathMetadataFactory.*;
import com.mysema.query.types.path.*;
import com.mysema.query.types.PathMetadata;
import javax.annotation.Generated;
import com.mysema.query.types.Path;
import com.mysema.query.types.path.PathInits;
/**
* QOrderHeader is a Querydsl query type for OrderHeader
*/
#Generated("com.mysema.query.codegen.EntitySerializer")
public class QOrderHeader extends EntityPathBase<OrderHeader> {
private static final long serialVersionUID = 2006939562;
public static final QOrderHeader orderHeader = new QOrderHeader("orderHeader");
public final com.stoecklin.utility.database.QBaseEntity _super = new com.stoecklin.utility.database.QBaseEntity(this);
public final DateTimePath<java.util.Date> actualDeliveryTime = createDateTime("actualDeliveryTime", java.util.Date.class);
public final StringPath creationMode = createString("creationMode");
//inherited
public final DateTimePath<java.util.Date> creationTime = _super.creationTime;
public final StringPath customerCode = createString("customerCode");
public final StringPath customerOrderCode = createString("customerOrderCode");
public final StringPath deliveryCode = createString("deliveryCode");
public final StringPath deliveryNote = createString("deliveryNote");
public final StringPath headerText = createString("headerText");
public final NumberPath<Integer> hostId = createNumber("hostId", Integer.class);
public final NumberPath<Long> id = createNumber("id", Long.class);
//inherited
public final DateTimePath<java.util.Date> lastUpdateTime = _super.lastUpdateTime;
public final StringPath orderCode = createString("orderCode");
public final StringPath orderGroup = createString("orderGroup");
public final ListPath<OrderPosition, QOrderPosition> orderPositions = this.<OrderPosition, QOrderPosition>createList("orderPositions", OrderPosition.class, QOrderPosition.class, PathInits.DIRECT2);
public final StringPath orderTypeCode = createString("orderTypeCode");
public final StringPath priority = createString("priority");
public final DateTimePath<java.util.Date> requestedDeliveryTime = createDateTime("requestedDeliveryTime", java.util.Date.class);
public final StringPath setupType = createString("setupType");
public final StringPath shippingMode = createString("shippingMode");
public final StringPath stagingArea = createString("stagingArea");
public final EnumPath<com.stoecklin.wms.enums.OrderHeaderState> stateCode = createEnum("stateCode", com.stoecklin.wms.enums.OrderHeaderState.class);
public final StringPath stateReason = createString("stateReason");
public final DateTimePath<java.util.Date> stateTime = createDateTime("stateTime", java.util.Date.class);
//inherited
public final NumberPath<Long> version = _super.version;
public QOrderHeader(String variable) {
super(OrderHeader.class, forVariable(variable));
}
public QOrderHeader(Path<? extends OrderHeader> path) {
super(path.getType(), path.getMetadata());
}
public QOrderHeader(PathMetadata<?> metadata) {
super(OrderHeader.class, metadata);
}
}
and
package com.stoecklin.wms.entity;
import static com.mysema.query.types.PathMetadataFactory.*;
import com.mysema.query.types.path.*;
import com.mysema.query.types.PathMetadata;
import javax.annotation.Generated;
import com.mysema.query.types.Path;
import com.mysema.query.types.path.PathInits;
/**
* QOrderPosition is a Querydsl query type for OrderPosition
*/
#Generated("com.mysema.query.codegen.EntitySerializer")
public class QOrderPosition extends EntityPathBase<OrderPosition> {
private static final long serialVersionUID = 2091670278;
private static final PathInits INITS = PathInits.DIRECT2;
public static final QOrderPosition orderPosition = new QOrderPosition("orderPosition");
public final com.stoecklin.utility.database.QBaseEntity _super = new com.stoecklin.utility.database.QBaseEntity(this);
public final StringPath articleCode = createString("articleCode");
//inherited
public final DateTimePath<java.util.Date> creationTime = _super.creationTime;
public final NumberPath<Integer> customerOrderPos = createNumber("customerOrderPos", Integer.class);
public final NumberPath<Float> deliveredQuantity = createNumber("deliveredQuantity", Float.class);
public final StringPath fromWarehouseCode = createString("fromWarehouseCode");
public final StringPath hostData = createString("hostData");
public final StringPath hostRef = createString("hostRef");
public final NumberPath<Long> id = createNumber("id", Long.class);
//inherited
public final DateTimePath<java.util.Date> lastUpdateTime = _super.lastUpdateTime;
public final StringPath lotCode = createString("lotCode");
public final NumberPath<Float> missingQuantity = createNumber("missingQuantity", Float.class);
public final QOrderHeader orderHeader;
public final NumberPath<Integer> orderPos = createNumber("orderPos", Integer.class);
public final StringPath ownerCode = createString("ownerCode");
public final StringPath posText = createString("posText");
public final NumberPath<Float> requestedQuantity = createNumber("requestedQuantity", Float.class);
public final EnumPath<com.stoecklin.wms.enums.OrderPositionState> stateCode = createEnum("stateCode", com.stoecklin.wms.enums.OrderPositionState.class);
public final StringPath stateReason = createString("stateReason");
public final DateTimePath<java.util.Date> stateTime = createDateTime("stateTime", java.util.Date.class);
public final StringPath toBePicked = createString("toBePicked");
public final StringPath toLocation = createString("toLocation");
//inherited
public final NumberPath<Long> version = _super.version;
public QOrderPosition(String variable) {
this(OrderPosition.class, forVariable(variable), INITS);
}
public QOrderPosition(Path<? extends OrderPosition> path) {
this(path.getType(), path.getMetadata(), path.getMetadata().isRoot() ? INITS : PathInits.DEFAULT);
}
public QOrderPosition(PathMetadata<?> metadata) {
this(metadata, metadata.isRoot() ? INITS : PathInits.DEFAULT);
}
public QOrderPosition(PathMetadata<?> metadata, PathInits inits) {
this(OrderPosition.class, metadata, inits);
}
public QOrderPosition(Class<? extends OrderPosition> type, PathMetadata<?> metadata, PathInits inits) {
super(type, metadata, inits);
this.orderHeader = inits.isInitialized("orderHeader") ? new QOrderHeader(forProperty("orderHeader")) : null;
}
}
Now the questions:
How do I have to do the join at the bottom of the ALL_ORDERS subquery? I already tried the following:
QOrderHeader orderHeader = QOrderHeader.orderHeader;
QOrderHeader orderHeaderGroup = new QOrderHeader("orderHeaderGroup");
QOrderPosition orderPosition = QOrderPosition.orderPosition;
List<Tuple> tuples = query.from(orderHeader)
.leftJoin(orderHeader, orderHeaderGroup).on(orderHeader.orderGroup.eq(orderHeaderGroup.orderGroup))
.list(
orderHeader.orderGroup,
orderHeader.id
);
but this even won't compile because there is no matching method leftJoin available.
The join is not on a FK column but on some arbitrary attribute. The subquery ORDER_GROUP
How is the correlated subquery implemented which computes the nof_orders_per_group (7th item in SELECT list)? (I have no clue how tho do this :-))
The SQL shown on the top is somewhat optimized for efficiency. For that reason I decided to use native SQL because we have subqueries all around.
Any help is appreciated.

If you want to use Querydsl with SQL then you need to create the metamodel in a different way, which is described here http://www.querydsl.com/static/querydsl/3.2.4/reference/html/ch02s03.html
And to your more specific questions:
1) FROM ORDERHEADER h left outer join ORDER_GROUP g on h.ordergroup = g.ordergroup
from(h).leftJoin(g).on(h.ordergroup.eq(g.ordergroup))
2) CASE
WHEN h.ordergroup IS NOT NULL
THEN g.nofOrdersPerGroup
ELSE 1
END AS nof_orders_per_group
new CaseBuilder()
.when(h.ordergroup.isNotNull()).then(g.nofOrderPerGroup)
.otherwise(1)
Concerning query construction, joins work different from JPA
Querydsl JPA
query.join(entity.property, reference)
Querydsl SQL
query.join(table).on(condition)
or alternatively
query.join(table.fk, otherTable)

Related

JPA Stored Procedure result set mappings and NonUniqueResultException

I'm fairly new to JPA and am trying to use a stored procedure to run a query and map its results to my java classes.
Here are the tables
CREATE TABLE dbo.Branding
(
Branding_ID INT IDENTITY NOT NULL
CONSTRAINT PK_Branding PRIMARY KEY CLUSTERED,
BrandingType_ID INT,
Reseller_ID INT NULL,
Host VARCHAR(MAX) NULL
)
CREATE TABLE dbo.BrandingResource
(
BrandingResource_ID INT IDENTITY NOT NULL
CONSTRAINT PK_BrandingResource PRIMARY KEY CLUSTERED,
Branding_ID INT NOT NULL,
Name VARCHAR(255) NOT NULL,
[Value] VARCHAR(MAX) NOT NULL
)
CREATE TABLE dbo.BrandingType
(
BrandingType_ID INT IDENTITY NOT NULL
CONSTRAINT PK_BrandingType PRIMARY KEY CLUSTERED,
Description VARCHAR(255)
)
Here are the entities:
#Table(name = "[Branding]")
#Entity
public class Branding extends CommonDomainBase
{
#Id
#Column(name = "branding_id")
private int id;
#OneToOne(optional = false)
#JoinColumn(name = "brandingtype_id", nullable = false)
private BrandingType type;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "branding_id", referencedColumnName = "branding_id")
private Set<BrandingResource> brandingResources;
#Column(name = "reseller_id", nullable = true)
private Integer resellerId;
#Column(name = "host", nullable = true)
private String host;
}
#Table(name = "[BrandingResource]")
#Entity
public class BrandingResource extends CommonDomainBase
{
#Id
#Column(name = "BrandingResource_Id")
private int id;
#Column(name = "Name")
private String name;
#Column(name = "Value")
private String value;
}
#Table(name = "[BrandingType]")
#Entity
public class BrandingType extends CommonDomainBase
{
#Id
#Column(name = "brandingtype_id")
private int id;
#Column(name = "description")
private String description;
}
I already know that the annotations on the entities are working correctly. When I use Spring Data JPA repositories to query the 3 tables to find one or find all of Branding, I get a generated query which retrieves all 3 tables in a single query.
I am now trying to extends this to allow me to do the same sort of result set mapping using a named stored procedure which I've configured in the following way:
#NamedStoredProcedureQuery(name = "Branding.getBrandingByHost", procedureName = "spGetBrandingByHost", parameters =
{ #StoredProcedureParameter(mode = ParameterMode.IN, name = "host", type = String.class) }, resultSetMappings =
{ "BrandingResults" })
#SqlResultSetMapping(name = "BrandingResults", entities =
{ #EntityResult(entityClass = Branding.class) })
The stored procedure is returning duplicate rows for each row in the branding table, due to the one to many relationship to BrandingResource.
The result set mapping when using the Spring Data JPA repository and it's generated query has duplicate rows in the same way as my procedure, and is able to handle this perfectly when mapping to the objects. When using the named stored procedure however I get the following exception:
javax.persistence.NonUniqueResultException: Call to stored procedure [spGetBrandingByHost] returned multiple results
I understand that I will probably need to include more result set mappings for this to work, but cannot find a example which demonstrates anything similar. Is what I'm after even possible?
Thanks in advance
In answer to my own question, no you can't. Which does make sense. When automatically generating queries, hibernate what column names to expect in the result set, including any that are duplicated from a one to many/many to one relationship. A stored procedure can return any column that hibernate does not know to expect, so setting them explicitly is required.
After much digging I did find the class org.hibernate.cfg.annotations.ResultsetMappingSecondPass which is called to map JPA annotations to native hibernate a org.hibernate.engine.ResultSetMappingDefinition and, reading the source code, I can see it completely ignores most of the annotations for columns, and joining.
It would be great if #NamedStoredProcedureQuery could support one to many/many to one joins. For now I've created my own solution:
public class EntityResultSetSecondPass implements QuerySecondPass
{
private static final String ALIAS = EntityResultSetSecondPass.class.getName() + "_alias";
private final InFlightMetadataCollector metadataCollector;
private int entityAliasIndex;
private final Map<Class<?>, String> aliasMap = new ConcurrentHashMap<>();
public EntityResultSetSecondPass(final InFlightMetadataCollector metadataCollector)
{
this.metadataCollector = metadataCollector;
}
#Override
public void doSecondPass(final Map persistentClasses) throws MappingException
{
for (final Object key : persistentClasses.keySet())
{
final String className = key.toString();
try
{
final Class<?> clazz = Class.forName(className);
final EntityResultSet entityResultSet = clazz.getDeclaredAnnotation(EntityResultSet.class);
if (entityResultSet == null)
{
continue;
}
else
{
createEntityResultDefinition(entityResultSet, clazz);
}
}
catch (final ClassNotFoundException e)
{
throw new HibernateException(e);
}
}
}
private void createEntityResultDefinition(final EntityResultSet entityResultSet, final Class<?> entityClass)
throws ClassNotFoundException
{
final List<NativeSQLQueryReturn> mappedReturns = new ArrayList<>();
final ResultSetMappingDefinition definition = new ResultSetMappingDefinition(entityResultSet.name());
final Map<Class<?>, FieldResult[]> returnedEntities = new ConcurrentHashMap<>();
returnedEntities.put(entityClass, entityResultSet.fields());
for (final EntityResult entityResult : entityResultSet.relatedEntities())
{
returnedEntities.put(entityResult.entityClass(), entityResultSet.fields());
}
definition.addQueryReturn(new NativeSQLQueryRootReturn(getOrCreateAlias(entityClass), entityClass.getName(),
getPropertyResults(entityClass, entityResultSet.fields(), returnedEntities, mappedReturns, ""),
LockMode.READ));
for (final EntityResult entityResult : entityResultSet.relatedEntities())
{
definition
.addQueryReturn(
new NativeSQLQueryRootReturn(getOrCreateAlias(entityResult.entityClass()),
entityResult.entityClass().getName(), getPropertyResults(entityResult.entityClass(),
entityResult.fields(), returnedEntities, mappedReturns, ""),
LockMode.READ));
}
for (final NativeSQLQueryReturn mappedReturn : mappedReturns)
{
definition.addQueryReturn(mappedReturn);
}
metadataCollector.addResultSetMapping(definition);
}
private Map<String, String[]> getPropertyResults(final Class<?> entityClass, final FieldResult[] fields,
final Map<Class<?>, FieldResult[]> returnedEntities, final List<NativeSQLQueryReturn> mappedReturns,
final String prefix) throws ClassNotFoundException
{
final Map<String, String[]> properties = new ConcurrentHashMap<>();
for (final Field field : entityClass.getDeclaredFields())
{
final Column column = field.getAnnotation(Column.class);
if (column != null)
{
properties.put(prefix + field.getName(), new String[]
{ column.name() });
}
final JoinColumn joinColumn = field.getAnnotation(JoinColumn.class);
if (joinColumn != null)
{
properties.putAll(handleJoinColumn(entityClass, field, joinColumn, returnedEntities, mappedReturns));
}
}
if (entityClass.getSuperclass() != null)
{
properties.putAll(
getPropertyResults(entityClass.getSuperclass(), fields, returnedEntities, mappedReturns, prefix));
}
return properties;
}
private Map<String, String[]> handleJoinColumn(final Class<?> sourceEntity, final Field field,
final JoinColumn joinColumn, final Map<Class<?>, FieldResult[]> returnedEntities,
final List<NativeSQLQueryReturn> mappedReturns) throws ClassNotFoundException
{
final Map<String, String[]> properties = new ConcurrentHashMap<>();
final OneToOne oneToOne = field.getAnnotation(OneToOne.class);
if (oneToOne != null)
{
properties.put(field.getName(), new String[]
{ joinColumn.name() });
}
final OneToMany oneToMany = field.getAnnotation(OneToMany.class);
if (oneToMany != null)
{
Class<?> fieldType;
if (field.getType().isArray())
{
fieldType = field.getType();
}
else if (Collection.class.isAssignableFrom(field.getType()))
{
fieldType = Class.forName(
ParameterizedType.class.cast(field.getGenericType()).getActualTypeArguments()[0].getTypeName());
}
else
{
throw new UnsupportedOperationException("One to many only supports collection and array types");
}
if (returnedEntities.keySet().contains(fieldType))
{
properties.put(field.getName(), new String[]
{ joinColumn.name() });
final Map<String, String[]> resolvedProperties = getPropertyResults(fieldType,
returnedEntities.get(fieldType), returnedEntities, mappedReturns, "element.");
resolvedProperties.put("key", new String[]
{ joinColumn.referencedColumnName() });
resolvedProperties.put("element", new String[]
{ joinColumn.name() });
mappedReturns.add(new NativeSQLQueryCollectionReturn(getOrCreateAlias(fieldType),
sourceEntity.getName(), field.getName(), resolvedProperties, LockMode.READ));
mappedReturns
.add(new NativeSQLQueryJoinReturn(getOrCreateAlias(fieldType),
getOrCreateAlias(sourceEntity), field.getName(), getPropertyResults(fieldType,
returnedEntities.get(fieldType), returnedEntities, mappedReturns, ""),
LockMode.READ));
}
}
return properties;
}
private String getOrCreateAlias(final Class<?> entityClass)
{
if (!aliasMap.containsKey(entityClass))
{
aliasMap.put(entityClass, ALIAS + entityAliasIndex++);
}
return aliasMap.get(entityClass);
}
}
and the accompanying annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface EntityResultSet
{
/**
* The name of the result set
*
* #return
*/
String name();
/**
* The {#link FieldResult} to override those of the {#link Column}s on the
* current {#link Entity}
*
* #return
*/
FieldResult[] fields() default {};
/**
* The {#link EntityResult} that define related {#link Entity}s that are
* included in this result set.
*
* </p>Note: discriminatorColumn has no impact in this usage
*
* #return
*/
EntityResult[] relatedEntities() default {};
}
This is all registered with hibernate via a MetadataContributor
The code is a bit of a mess, but it is actually working. It basically looks for #EntityResultSet in which the entities for a particular result set are defined. The EntityResultSetSecondPass looks at these given entities and generates a ResultSetMappingDefinition which includes all the joining meta data for collection mapping. It runs from all the standard column annotations but can be overridden with FieldResult defined in #EntityResultSet
It seems a bit nasty, but it's working nicely.

Is it possible to get the SQL alias of a join table for a Order by using Hibernate criteria?

I have this type of query
Critetria criteria = session.createCriteria(Perosn.class,"person");
criteria.createAlias("person.cityInformation","city",JoinType.LEFT_OUTER_JOIN);
criteria.createAlias("city.streetInformation","street",JoinType.LEFT_OUTER_JOIN);
criteria.add(new NativeSQLOrder("field({alias}.id,3,2,1)",true)));
NativeSQLOrder.java
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.annotations.common.util.StringHelper;
import org.hibernate.criterion.CriteriaQuery;
import org.hibernate.criterion.Order;
public class NativeSQLOrder extends Order
{
private static final long serialVersionUID = 1L;
private final static String PROPERTY_NAME = "uselessAnyways";
private boolean ascending;
private String sql;
protected NativeSQLOrder(String sql, boolean ascending)
{
super(PROPERTY_NAME, ascending);
this.sql = sql;
this.ascending = ascending;
}
#Override
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException
{
StringBuilder fragment = new StringBuilder();
fragment.append("(");
fragment.append(sql);
fragment.append(")");
fragment.append(ascending ? " asc" : " desc");
return StringHelper.replace(sql, "{alias}", criteriaQuery.getSQLAlias(criteria));
}
}
I want order by on id of street field but using this I am getting alias of first table Person.Is there any way that I will get alias of Street table.
It seems the CriteriaQuery only allows recovering the root table alias.
https://docs.jboss.org/hibernate/orm/3.2/api/org/hibernate/criterion/CriteriaQuery.html#getSQLAlias%28org.hibernate.Criteria%29

How to build query with selecting by value of foreign object's field

What is the best way for querying by using the value of foreign object's field?
Suppose I have these three classes.
UnitResult class which describes amount of Units:
#DatabaseTable
public class UnitResult {
public static final String ID_FIELD_NAME = "id";
public static final String UNIT_COLUMN_NAME = "unit";
public static final String RESULT_COLUMN_NAME = "result";
#DatabaseField(generatedId = true, columnName = ID_FIELD_NAME)
public Integer id;
#DatabaseField(foreign = true, canBeNull = false, columnName = UNIT_COLUMN_NAME)
public Unit unit;
#DatabaseField(canBeNull = true, columnName = RESULT_COLUMN_NAME)
public Integer result = null;
}
Unit class which describes certain Units in a market (for example jiuce, snack etc.):
#DatabaseTable
public class Unit {
public static final String ID_FIELD_NAME = "id";
public static final String TYPE_FIELD_NAME = "type";
#DatabaseField(id = true, columnName = ID_FIELD_NAME)
public int id;
#DatabaseField(canBeNull = false, columnName = TYPE_FIELD_NAME)
public UnitType type;
}
And Enum of Unit type:
public enum UnitType {
JUICES,
DRINKS,
SNACKS,
NPD;
}
So how can I query all UnitResult where Unit type is UnitType.JUICES?
So how can I query all UnitResult where Unit type is UnitType.JUICES?
The way to do this in ORMLite is to use the `Where.in(...) with a sub-query:
// setup our sub-query on the Unit table first
QueryBuilder<Unit,Integer> uQb = unitDao.queryBuilder();
uQb.where().eq(Unit.TYPE_FIELD_NAME, UnitType.JUICES);
// outer query on UnitResult table
QueryBuilder<UnitResult,Integer> urQb = unitResultDao.queryBuilder();
// in using the sub-query
urQb.where().in(UnitResult.UNIT_COLUMN_NAME, uQb);
List<UnitResult> results = urQb.query();

Hibernate 4.0: javax.persistence.criteria.Path.get fails for when field of a composite key is specified

Calling javax.persistence.criteria.Path.get(String name) fails for the simple class hierarchy detailed below. The call succeeds if #IdClass and 2nd id field (i.e. id2) are removed. Anyone know why this is so. Does this mean it is not possible to query on a single id field where that id field forms part of a composite key?
failing call is: Path<Object> path = entity.get(name);
private static final EntityManager em;
private final CriteriaBuilder cb = em.getCriteriaBuilder();
private final CriteriaQuery<Y> query = cb.createQuery(Y.class);
private final Root<Y> entity = query.from(Y.class);
static {
Map<String, Object> properties = new HashMap<String, Object>();
// initialise properties appropriately
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("test", properties);
em = emf.createEntityManager();
}
interface PK {
Object getPK();
}
public static class YPK implements Serializable {
int id;
int id2;
YPK(int id, int id2) { }
// Override hashCode() and equals() appropriately
}
#IdClass(YPK.class)
#Entity
public static class Y implements Serializable, PK {
#Id
int id;
#Id
int id2;
protected Y() { }
public Y(int id) {
this.id = id;
}
#Override
public Object getPK() {
return id;
}
}
#Test
public void simpleTest() {
List<Y> yy = new ArrayList<Y>();
Y yX1 = new Y(5);
yy.add(yX1);
Y yX2 = new Y(6);
yy.add(yX2);
saveItems(yy);
String name = "id";
Path<Object> path = entity.get(name);
Predicate restriction = cb.conjunction();
restriction = cb.and(restriction, cb.and(new Predicate[]{cb.equal(path, 5)}));
TypedQuery<Y> tq = em.createQuery(this.query);
Y result = null;
try {
result = tq.getSingleResult();
} catch (NoResultException e) {
}
assertNotNull(result);
}
To access fields that are members of the IdClass you need to use metamodel.
I suggest to go with static metamodel, because it is cleaner and kind of type safe. You can generate it with the tools or write it by yourself. For class Y it will be something like:
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
#StaticMetamodel(Y.class)
public abstract class Y_ {
public static volatile SingularAttribute<Y, Integer> id;
public static volatile SingularAttribute<Y, Integer> id2;
// + similar definitions for other fields:
// <TYPE_OF_ENTITY, TYPE_OF_ATTRIBUTE> NAME_OF_FIELD_IN_ENTITY
}
Then you can use IdClass fields in criteria query:
Path<Integer> pathToId = entity.get(Entity2_.id);
Path<Integer> pathToId2 = entity.get(Entity2_.id2);
If you don't want to generate static metamodel, then there is still following rather bad way to access attributes of id:
//find set of all attribute that form id
Metamodel mm = em.getMetamodel();
EntityType et = mm.entity(Y.class);
Set<SingularAttribute> idAttributes = et.getIdClassAttributes();
SingularAttribute idAttribute = null;
//and pick out right one from [id, id2]
for (SingularAttribute candidate : idAttributes) {
if (candidate.getName().equals("id")) {
idAttribute = candidate;
break;
}
}
Path<Integer> path = entity.get(idAttribute);

ContentProvider.Query - How to select the table I want using URIs?

I'm currently facing a database query issue. I have two tables, items and details with the following schema definition:
items(_id, title, type) <--- _id is primary key
details(_id, _itemid, name, quantity, dosis) <--- _id is primary key. _itemid is foreign key
Furthermore I access my database via. my own contentProvider. In my activity I want to query the details table to get all tuples, however Im not sure how to "get" the table using URI's. My query is:
Uri subItems = Uri.parse("content://tod.dosetracker.provider.Food");
Cursor c = getContentResolver().query(
subItems,
new String[] {"*"},
"_itemid = " + _id,
null,
null);
In my ContentProvider class I have the following (extract):
public static final String PROVIDER_NAME = "tod.dosetracker.provider.Food";
public static final Uri CONTENT_URI_ITEMS = Uri.parse("content://" + PROVIDER_NAME + "/items");
public static final Uri CONTENT_URI_DETAILS = Uri.parse("content://" + PROVIDER_NAME + "/details");
public static final String _ID = "_id";
public static final String _ITEMID = "_itemid";
public static final String TITLE = "title";
public static final String TYPE = "type";
public static final String NAME = "name";
public static final String DOSIS = "dosis";
public static final String QUANTITY = "quantity";
private static final int ITEM = 1;
private static final int ITEM_ID = 2;
private static final int DETAILS = 3;
private static final int DETAILS_ID = 4;
// URI resource matchers
private static final UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "items", ITEM);
uriMatcher.addURI(PROVIDER_NAME, "items/#", ITEM_ID);
uriMatcher.addURI(PROVIDER_NAME, "details", DETAILS);
uriMatcher.addURI(PROVIDER_NAME, "details/#", DETAILS_ID);
}
// Database reference name
private SQLiteDatabase foodDB;
You are using the wrong content uri. Use the one you defined for your items instead(if you want items)
FoodContentProvider.CONTENT_URI_ITEMS
So your query would look like this (oh BTW just put null if you want all columns);
Uri subItems = Uri.parse("content://tod.dosetracker.provider.Food");
Cursor c = getContentResolver().query(
CONTENT_URI_ITEMS,
null,
"_itemid = " + _id,
null,
null);

Categories