How to avoid N+1 problem with native SQL query in springboot with Hibernate? - postgresql

I'm querying my DB with POSTGIS built-ins to retrieve the closest Machines given a Location.
I have to use a native SQL because Hibernate does not support POSTGIS and CTEs:
#Repository
public interface MachineRepository extends JpaRepository<Machine, Long>{
#Query(value =
"with nearest_machines as\n" +
" (\n" +
" select distance_between_days(:id_day, machine_availability.id_day) as distance_in_time,\n" +
" ST_Distance(geom\\:\\:geography, ST_SetSrid(ST_MakePoint(:longitude, :latitude), 4326)\\:\\:geography) as distance_in_meters,\n" +
" min(id_day) over (partition by machine.id) as closest_timeslot_per_machine,\n" +
" machine_availability.id_day,\n" +
" machine.*\n" +
" from machine\n" +
" join machine_availability on machine.id = machine_availability.id_machine\n" +
" where machine_availability.available = true\n" +
" and machine_availability.id_day >= :today\n" +
" and ST_DWithin(geom\\:\\:geography, ST_SetSrid(ST_MakePoint(:longitude, :latitude), 4326)\\:\\:geography, 1000)\n" +
" )\n" +
"select nearest_machines.*\n" +
"from nearest_machines\n" +
"where id_day = closest_timeslot_per_machine\n" +
"order by distance_in_time, distance_in_meters\n" +
"limit 20;",
nativeQuery = true)
List<Machine> findMachinesAccordingToAvailabilities(#Param("longitude") BigDecimal longitude,
#Param("latitude") BigDecimal latitude,
#Param("id_day") String idDay,
#Param("today") String today);
}
Of course, Machine and MachineAvailability are #Entity's. And they are #OneToMany(fetch = FetchType.EAGER) related. I changed the default LAZY to EAGER cause i need the MachineAvailability in the final JSON.
The problem is that it triggers 2 more requests by resulting machine(ie the famous N+1 problem).
1.How can i solve that in only ONE request?
2.Is it possible to create my on JSON somehow and returning it directly in the MachineController?

Solving this in 1 request is tough as you will have to use Hibernate native APIs to map the table aliases for the availability collection. You would need to add a join for the availabilities in the main query and do something like this: session.createNativeQuery("...").addEntity("m", Machine.class).addFetch("av", "m", "availabilities")
Another alternative would be to use Blaze-Persistence Entity Views since Blaze-Persistence comes with support for CTEs and many more goodies that PostgreSQL provides, this might be an interesting solution for you.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
I don't know your model, but a possible DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Machine.class)
#With(NearestMachineCteProvider.class)
#EntityViewRoot(name = "nearest", entity = NearestMachine.class, condition = "machineId = VIEW(id)", joinType = JoinType.INNER)
public interface MachineDto {
#IdMapping
Integer getId();
String getName();
#Mapping("nearest.distanceInTime")
Integer getDistanceInTime();
#Mapping("nearest.distanceInMeters")
Double getDistanceInMeters();
Set<MachineAvailabilityDto> getAvailabilities();
#EntityView(MachineAvailability.class)
interface MachineAvailabilityDto {
#IdMapping
Integer getId();
String getName();
}
class NearestMachineCteProvider implements CTEProvider {
#Override
public void applyCtes(CTEBuilder<?> builder, Map<String, Object> optionalParameters) {
builder.with(NearestMachine.class)
.from(Machine.class, "m")
.bind("distanceInTime").select("CAST_INTEGER(FUNCTION('distance_between_days', :id_day, m.availabilities.idDay))")
.bind("distanceInMeters").select("CAST_DOUBLE(FUNCTION('ST_Distance', m.geom, FUNCTION('ST_SetSrid', FUNCTION('ST_MakePoint', :longitude, :latitude), 4326)))")
.bind("closestTimeslotId").select("min(m.availabilities.idDay) over (partition by m.id)")
.bind("machineId").select("m.id")
.bind("machineAvailabilityDay").select("m.availabilities.idDay")
.where("m.availabilities.available").eqLiteral(true)
.where("m.availabilities.idDay").geExpression(":today")
.where("FUNCTION('ST_DWithin', m.geom, FUNCTION('ST_SetSrid', FUNCTION('ST_MakePoint', :longitude, :latitude), 4326), 1000)").eqLiteral(true)
.end();
}
}
}
#CTE
#Entity
public class NearestMachine {
private Integer distanceInTime;
private Double distanceInMeters;
private Integer closestTimeslotId;
private Integer machineId;
private Integer machineAvailabilityDay;
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
MachineDto a = entityViewManager.find(entityManager, MachineDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<MachineDto> findAll(Pageable pageable);
You can then sort by using Sort.asc("distanceInTime") and Sort.asc("distanceInMeters")
The best part is, it will only fetch the state that is actually necessary!

Related

How to Use Values from an #ElementCollection Map in JPQL Where Clause

I have an entity with a Map<String,String> field that is an #ElementCollection. I'd like to select entities whose values for this map field match some pattern. However, I'm getting an error at runtime, in this case, with an H2 database.
Here's the entity:
#Entity
#Table(name = "config")
public class Config {
private String id;
private String name;
#ElementCollection
#CollectionTable(name = "setting",
joinColumns = #JoinColumn(name = "config_id", referencedColumnName = "id"))
#MapKeyColumn(name = "name", columnDefinition = "varchar(1024)")
#Column(name = "value", columnDefinition = "TEXT")
Map<String, String> settings;
}
Then, in my JPA repository, I have a method that fetches configs which have a setting that matches a given prefix:
#Query("select config.id, config.name from Config config " +
"join config.settings setting " +
"where value(setting) like concat(:prefix, '%')")
List<List<String>> findConfigBySettingPrefix(#Param("prefix") String prefix);
This produces an error from H2:
Caused by: org.h2.jdbc.JdbcSQLDataException: Scalar subquery contains more than one row;
The SQL generated by the JPQL shows that the value() function is expanded into a subselect:
select config."id", config."name" from "config" config
inner join "setting" setting on config."id"=setting."config_id"
where (select setting."value" from "setting" setting where config."id"=setting."config_id") like (?||'%')
Thus, when there are multiple matching settings (as expected), this produces an error because they are being passed into an operator that expects one value. When I wrote the JPQL, I was expecting it would generate something more like this:
select config."id", config."name" from "config" config
inner join "setting" setting on config."id"=setting."config_id"
where setting."value" like (?||'%')
It would seem I'm not able to use value() as a where condition, or I'm just not doing it right. Is there a better way to do this?
I've tried similar variants that all compile at bootup, but they produce the same error at runtime:
#Query("select config.id, config.name from Config config " +
"join config.settings setting on value(setting) like concat(:prefix, '%')")
#Query("select config.id, config.name from Config config " +
"where value(config.settings) like concat(:prefix, '%')")
This doesn't precisely answer the question, but it is an alternative to using JPQL. Using Spring Data query semantics in the repository method name produces the results I'm looking for:
public interface ConfigRepository extends JpaRepository<Config, String> {
List<Config> findAllBySettingsStartingWith(String prefix);
}

How to map ALL names directly by JPA?

Given a ZIP-code-like hierarchical code/name schema.
For example:
code = 101010
Code:
100000 level 1 code (10....)
101000 level 2 code (..10..)
101010 level 3 code (....10)
Name (short name)
100000 - A
101000 - a
101010 - i
Name (FullQualifiedName)
100000 - A
101000 - A->a
101010 - A-a->i
EDIT
I wanna following code (JPA pseudo code), but CANNOT.
#Entity
public class CodeName{
// ....
String code; // 100101 levels = {100000, 100100, 100101}
String name; //
#HowToMapDirectedToNameOfCode('100000') // #SecondTable ?
String name1;
#HowToMapDirectedToNameOfCode('100100')
String name2;
#HowToMapDirectedToNameOfCode('100101')
String name3;
String getFullQualifiedName(){
return String.format("%s->%s->%s", name1, name2, name3);
}
// getter and setter
}
But it's relatively easier in native SQL:
SELECT (select p1.name from codename p1 where p1.code= concat( substring(p.code,1,2), "0000") ) province,
(select p2.name from codename p2 where p2.code= concat( substring(p.code,1,4), "00") ) city,
(select p3.name from codename p3 where p3.code=p.code) area
FROM codename p WHERE p.code = '100101';
So, I implements it as following snippet.
#Entity
public class CodeName{
// ....
String code; // 100000, 101000, 100101
String name; // province, city , area
#Transient
String name1; // mapping directly?
#Transient
String name2; // mapping directly?
#Transient
String name3; // mapping directly?
String getFullQualifiedName(){
return String.format("%s->%s->%s", name1, name2, name3);
}
// getter and setter
}
public interface CodeNameRepository extends CrudRepository<CodeName, Long>, CodeNameRepositoryCustom {
#Query(" FROM CodeName p " +
" WHERE p.code = CONCAT(SUBSTRING(?1, 1, 2), '0000') " +
" OR p.code = CONCAT(SUBSTRING(?1, 1, 4), '00') " +
" OR p.code = ?1")
List<CodeName> findAllLevelsByCode(String code);
}
#Component
public class CodeNameRepositoryImpl implements CodeNameRepositoryCustom {
#Autowired
private CodeNameRepository codeNameRepository ;
#Override
public CodeName CodeNamefindFullQualifiedNameByCode(String code) {
List<CodeName> codeNames= codeNameRepository .findAllLevelsByCode(code);
CodeName codeName;
// extra name1, name2, name3 from list,
// fill code, name, name1, name2, name3 to codeName and
return codeName;
}
}
But it have SO MANY limitations.
Most likely, I need getFullQualifiedName(), to display it on UI, but every time I must have an extra call to populate all names.
For each entity has CodeName as its children, no matter how deep the codeName is at, I MUST expand to the codeName and reload it with FQN.
Can we mapping all #Transient names directly by JPA?
You could technically model your code repository entity as follows:
public class CodeName {
#Id
#GeneratedValue(GenerationStrategy.AUTO)
#Column
private Long id;
#ManyToOne
private CodeName parent;
#OneToMany(mappedBy = "parent")
private List<CodeName> children;
#Column
private String name;
#Transient
public String getFullyQualifiedName() {
List<String> names = new ArrayList<>();
names.add(name);
CodeName theParent = parent;
while(theParent != null) {
names.add(theParent.getName());
theParent = theParent.parent;
}
Collections.reverse(names);
return StringUtils.join(names, "->");
}
}
Because the parent relationships will be fetched EAGERLY because they mapped as #ManyToOne, you can basically start at any child CodeName entity and traverse up it's parent/child relationship to the root. This basically allows the getFullyQualifiedName method to build the name for you at runtime.
If performance becomes a problem doing this, you can always datamine the names ahead of time in your entity as you described by adding a #Column private String fullyQualifiedName and make sure that field is inserted when you create your codes. Then the transient method I added to my the entity can be dropped since you're caching the names at data insertion.
It is possible to write a JPQL, which is equivalent to your SQL query. The only tricky part is to rewrite nested selects into cross joins, because nested selects are not supported by JPA and you need to join unrelated entities. On the other hand, functions CONCAT and SUBSTRING are supported by JPQL in the same way as in SQL. See the following JPQL query, which should give you the results as the SQL query in the question:
SELECT p1.name // province
, p2.name // city
, p.name // area
FROM CodeName p, CodeName p1, CodeName p2
WHERE p.code = '100101'
AND p1.code = concat( substring(p.code,1,2), "0000")
AND p2.code= concat( substring(p.code,1,4), "00")
The above query will give you 3 values in one row, which cannot be mapped into a single entity. The result of the query will therefore be a list of Object[] arrays. You may also add the original entity into the select clause: SELECT p1.name, p2.name, p.name, p FROM .... This way, you may later process the list of results and assign first three values into the transient fields of the entity:
Object[] rows = query.getResultList();
for (Object row : rows) {
CodeName c = (CodeName)row[3];
c.setName1((String)row[0]);
c.setName2((String)row[1]);
c.setName3((String)row[2]);
}

Error while casting in JPQL

This is on a JSF + JPA web application.
When I used the following JPQL query which include casting, it gives an error difficult to understand.
This is the query
jpql = "select new data.dataStructure.PharmacyStockRow(amp.vmp.name, sum(s.stock), "
+ "sum(s.itemBatch.purcahseRate * s.stock), sum(s.itemBatch.retailsaleRate * s.stock))"
+ "from Stock s join treat(s.itemBatch.item as Amp)amp "
+ "where s.stock>:z and s.department=:d "
+ "group by amp.vmp ";
m.put("d", department);
m.put("z", 0.0);
This is the class created just to receive the result from the query.
package data.dataStructure;
public class PharmacyStockRow {
String code;
String name;
Double qty;
Double purchaseValue;
Double saleValue;
public PharmacyStockRow() {
}
public PharmacyStockRow(String name, Double qty, Double purchaseValue, Double saleValue) {
this.name = name;
this.qty = qty;
this.purchaseValue = purchaseValue;
this.saleValue = saleValue;
}
I have a Stock class which stores data about stocks. It has a ItemBatch class which represent a batch. Batch has an Item. That item is actually a Amp, which extends Item. Amp has a Vmp. I want to get the stocks by Vmp. It would have been very easy if ItemBatch has a property of Amp, but I had to use more generic Item as the reference. So I need to cast Item to Amp within the Java Resistance Query. I tried it but it gives following error.
I use EclipseLink 2.5 as the Persistance Provider.
Caused by: Exception [EclipseLink-6034] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.QueryException
Exception Description: Invalid query item expression [
Query Key vmp
Query Key item (entity.pharmacy.Amp)
Query Key itemBatch
Base entity.pharmacy.Stock].
Query: ReportQuery(referenceClass=Stock jpql="select new data.dataStructure.PharmacyStockRow(amp.vmp.name, sum(s.stock), sum(s.itemBatch.purcahseRate * s.stock), sum(s.itemBatch.retailsaleRate * s.stock)) from Stock s join treat(s.itemBatch.item as Amp)amp where s.stock>:z and s.department=:d group by amp ")
at org.eclipse.persistence.exceptions.QueryException.invalidExpressionForQueryItem(QueryException.java:622)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.getLeafDescriptorFor(ObjectLevelReadQuery.java:1460)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.getLeafMappingFor(ObjectLevelReadQuery.java:1408)
at org.eclipse.persistence.internal.queries.ReportItem.initialize(ReportItem.java:124)
at org.eclipse.persistence.queries.ConstructorReportItem.initialize(ConstructorReportItem.java:137)
at org.eclipse.persistence.queries.ReportQuery.prepare(ReportQuery.java:1044)
at org.eclipse.persistence.queries.DatabaseQuery.checkPrepare(DatabaseQuery.java:613)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.checkPrepare(ObjectLevelReadQuery.java:823)
at org.eclipse.persistence.queries.DatabaseQuery.prepareCall(DatabaseQuery.java:1741)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.buildEJBQLDatabaseQuery(EJBQueryImpl.java:268)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.buildEJBQLDatabaseQuery(EJBQueryImpl.java:190)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.<init>(EJBQueryImpl.java:142)
at org.eclipse.persistence.internal.jpa.EJBQueryImpl.<init>(EJBQueryImpl.java:126)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1475)
at org.eclipse.persistence.internal.jpa.EntityManagerImpl.createQuery(EntityManagerImpl.java:1497)
I failed to solved the issue, but did a work around. I add all the additional methods in the child class into the parent class so that the casting was not necessary.

How to use MySQL's full text search from JPA

I want to use MySQL's full text search features using JPA, without having to use a native query.
I am using EclipseLink, which has a function to support native SQL commands: FUNC. However, the help examples only show this being use with simple MySQL functions. My best effort attempt to get it to work with MATCH & AGAINST is as follows:
#PersistenceContext(name="test")
EntityManager em;
Query query = em.createQuery("SELECT person FROM People person WHERE FUNC('MATCH', person.name) FUNC('AGAINST', :searchTerm)");
...
query.getResultList();
Which gives the following exception:
Caused by: NoViableAltException(32#[()* loopback of 822:9: (m= MULTIPLY right= arithmeticFactor | d= DIVIDE right= arithmeticFactor )*])
at org.eclipse.persistence.internal.libraries.antlr.runtime.DFA.noViableAlt(DFA.java:159)
at org.eclipse.persistence.internal.libraries.antlr.runtime.DFA.predict(DFA.java:116)
at org.eclipse.persistence.internal.jpa.parsing.jpql.antlr.JPQLParser.arithmeticTerm(JPQLParser.java:4557)
... 120 more
I am open to alternatives other that using the FUNC method.
I am using EJB 3 and EclipseLink 2.3.1.
An improved answer of #Markus Barthlen which works for Hibernate.
Create custom dialect
public class MySQLDialectCustom extends MySQL5Dialect {
public MySQLDialect() {
super();
registerFunction("match", new SQLFunctionTemplate(StandardBasicTypes.DOUBLE,
"match(?1) against (?2 in boolean mode)"));
}
}
and register it by setting hibernate.dialect property.
Use it
in JPQL:
Query query = entityManager
.createQuery("select an from Animal an " +
"where an.type = :animalTypeNo " +
"and match(an.name, :animalName) > 0", Animal.class)
.setParameter("animalType", "Mammal")
.setParameter("animalName", "Tiger");
List<Animal> result = query.getResultList();
return result;
or with Criteria API:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Animal> criteriaQuery = criteriaBuilder.createQuery(Animal.class);
Root<Animal> root = criteriaQuery.from(Animal.class);
List<Predicate> predicates = new ArrayList<>();
Expression<Double> match = criteriaBuilder.function("match", Double.class, root.get("name"),
criteriaBuilder.parameter(String.class, "animalName"));
predicates.add(criteriaBuilder.equal(root.get("animalType"), "Mammal"));
predicates.add(criteriaBuilder.greaterThan(match, 0.));
criteriaQuery.where(predicates.toArray(new Predicate[]{}));
TypedQuery<Animal> query = entityManager.createQuery(criteriaQuery);
List<Animal> result = query.setParameter("animalName", "Tiger").getResultList();
return result;
Some more details in this blog post: http://pavelmakhov.com/2016/09/jpa-custom-function
FUNC only works with normal printed functions,
i.e.
MATCH(arg1, arg2)
since MATCH arg1 AGAINST arg2 is not printed the way a function is normally printed, FUNC cannot be used to call it.
EclipseLink ExpressionOperators do support printing functions like this, so you could define your own ExpressionOperator, but ExpressionOperators are only supported through EclipseLink Expression queries currently, not through JPQL. You could log an enhancement to have operator support in JPQL.
You could also use a native SQL query.
Just to complete the answer: I had the same problem, but using the criteria builder. This is how you can get around the limitations in the standart implementation, if you are using EclipseLink:
Cast JPA expression to EclipseLink expression
Use the sql method
If you match against a compound index, create it using the function method
Example:
JpaCriteriaBuilder cb = (JpaCriteriaBuilder) cb;
List<String> args = new ArrayList();
args.add("Keyword");
Expression<Boolean> expr = cb.fromExpression (
cb.toExpression(
cb.function("", String.class,
table.get(Table_.text1), table.get(Table_.text2))
)
.sql("MATCH ? AGAINST (?)", args)
);
query.where(expr);
If you need to cast the expression to a predicate use the following:
query.where( cb.gt(expr, 0));
What about new SQL operator in EclipseLink 4.0? I think it can help you to do fulltext search from JPQL. But you have to upgrade to EclipseLink 4.0.
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/Support_for_Native_Database_Functions#SQL
Edit:
Sorry for late update.
Verified correct use of EclispeLink 2.4.0 "SQL" operator with MySQL fulltext search is
SELECT person FROM People person WHERE SQL('MATCH(name) AGAINST( ? )', :searchTerm)"
where name is column on which Fulltext index is defined. :searchTerm is string you use for searching.
Works without problems.
To elaborate on the answer of James:
It seems like I had luck extending the mysql dialect using
registerFunction("match", new SQLFunctionTemplate(DoubleType.INSTANCE, "match(?1) against (?2 in boolean mode)"));
and invoking the function via the following jpql fragment
match(" + binaryDataColumn + ",'" + StringUtils.join(words, " ") + "') > 0
I had to guess the return type, but this should get you started.
FInally work
if you set your table colums wit index full search
#NamedNativeQuery(name = "searchclient",
query = "SELECT * FROM client WHERE MATCH(clientFullName, lastname, secondname, firstphone,"
+ " secondphone, workphone, otherphone, otherphone1,"
+ " otherphone2, detailsFromClient, email, company,"
+ " address, contractType, paymantCondition) AGAINST(?)",
List list = em.createNamedQuery("searchclient").setParameter(1, searchKey).getResultList();
The simplest variant is to use NativeQuery
Example of use it with mapping to JPA entity (FiasAddress):
public class FiasServiceBean implements FiasService {
#PersistenceContext(unitName = "fias")
EntityManager entityManager;
#Override
public Collection<FiasAddress> search(String name, int limit, int aolevel) {
Query query = entityManager.createNativeQuery(
"SELECT fa.* FROM fias.addressobject fa" +
" WHERE MATCH(FORMALNAME) AGAINST (:name IN NATURAL LANGUAGE MODE)" +
" AND AOLEVEL = :AOLEVEL" +
" LIMIT :limit",
FiasAddress.class
);
query.setParameter("name", name);
query.setParameter("limit", limit);
query.setParameter("AOLEVEL", aolevel);
Iterator iterator = query.getResultList().iterator();
ArrayList<FiasAddress> result = new ArrayList<>();
while (iterator.hasNext()) {
result.add((FiasAddress) iterator.next());
}
return result;
}
}

jpa call readonly composite table but getting "Exception Description: Missing descriptor for [CollectorInfo]"

In a Spring 3 app a controller is calling a JpaCollectorManager with calls a JpaCollectorInfoDao to get a list which is defined by a nativequery. The query calls 2 seperate tables which uses sql and jpql because I need to use a postgresql feature not implemented in jpql. When the controller tries to file the list I get the following error message:
Exception [EclipseLink-6007] (Eclipse Persistence Services - 2.1.2.v20101206-r8635): org.eclipse.persistence.exceptions.QueryException
Exception Description: Missing descriptor for [CollectorInfo].
Query: ReadAllQuery(referenceClass=CollectorInfo sql="select distinct ON ( col.collector_id,pst.process_source_type ) col.*,pst.process_source_timestamp,pst.process_source_type from perform_schema.collector col join perform_schema.process_set pst on pst.collector_id = col.collector_id order by col.collector_id, pst.process_source_type,pst.process_source_timestamp desc ")
The controller Java has the following call:
List<CollectorInfo> ps = this.collectorInfoManager.getLatestCollectorInfo();
The JpaCollectorInfoManager.java has this:
public List<CollectorInfo> getLatestCollectorInfo()
{
return collectorInfoDao.getLatestCollectorInfo();
}
The JpaCollectorInfoDao.java:
#Override
#Transactional
public List<CollectorInfo> getLatestCollectorInfo() {
Query query = entityManager.createNativeQuery( ( "select distinct ON ( col.collector_id," +
"pst.process_source_type ) " +
"col.*," +
"pst.process_source_timestamp," +
"pst.process_source_type " +
"from perform_schema.collector col " +
"join perform_schema.process_set pst " +
"on pst.collector_id = col.collector_id " +
"order by col.collector_id, " +
"pst.process_source_type," +
"pst.process_source_timestamp desc " ),
CollectorInfo.class );
return ( (List<CollectorInfo>) query.getResultList() );
}
The CollectorInfo class does not have an #Entity defined. If I set the #Entity defined then it tells me that the Table cannot be resolved (which is correct since the there is no actual table). I have tried all sorts of permutations and cannot seem to make this needle thread.
Not sure what you are trying to do exactly?
You need to map the class as an Entity in order to be able to select instances of it.
Either, do not include the class, the native SQL query will an Object[] of the data, which you can map in your own code to your class.
Or map it as an Entity excepting the data that you are returning. The #Table will not be relevant as you are mapping the object to the query results. This should not cause any errors though, unless you are auto creating table or using integrity checker.
Or map the objects to the table correctly. Then use a fetch join, or batch fetch to optimize your retrieval if required.