I got the following entity:
#Entity
#Immutable
#Subselect("select distinct r.code, r.dsc\r\n"
+ "from roles r, accesses a\r\n"
+ "where r.code = '12'\r\n")
#MasterEntity(labelCode = "Scenario")
public class Scenario implements java.io.Serializable {
I want to use a dynamic query (or parameterized) instead of this static query.
for example: r.code should be equal to a parameter variable not "12"
is there a replacement for #Subselect while I use #Immutable?
Related
I have an SQL database with two related tables, my_entities and custom_entities.
The my_entities table has the columns id, custom_entity_id.
In my Groovy code, both are handled using Entity classes:
#Entity
class MyEntity {
#ManyToOne(fetch = EAGER)
#JoinColumn(name = "custom_entity_id")
CustomEntity customEntity
[...]
}
and
class CustomEntity {
#Id
#GeneratedValue(generator = "pooled")
#GenericGenerator(name = "pooled", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = [
#Parameter(name = "value_column_name", value = "sequence_next_hi_value"),
#Parameter(name = "prefer_entity_table_as_segment_value", value = "true"),
#Parameter(name = "optimizer", value = "pooled"),
#Parameter(name = "increment_size", value = "100"),
#Parameter(name = "initial_value", value = "100")])
Long id
[...]
}
I have a JPA repository MyEntityRepository class for querying the my_entity table:
#Repository
interface MyEntityRepository extends JpaRepository<MyEntity, Long> {
[...]
}
In MyEntityRepository, I am trying to implement a findByCustomEntityIds() method like this:
#Query("""
select e
from MyEntity e
where e.deleted = false
and e.customEntity in :ids
""")
List<TestCollectionQuery> findByCustomEntityIds(#Param("ids") List<Long> ids)
In MySQL, the query is rather simple (for instance with id 2400), clearly no joins required:
SELECT t.* FROM my_entities t WHERE custom_entity_id in (2400);
Apart from the automatic translation from underscores to camel case, the trailing _id in the SQL table column name is stripped; otherwise, the code does not compile.
I also have a custom Groovy class which calls that method:
class MyClass {
#Autowired
MyEntityRepository myEntityRepository
List<MyEntity> getEntities(List<Long> customEntityIds) {
return myEntityRepository.findByCustomEntityIds(customEntityIds)
}
The call raises the following exception:
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value element [2400] did not match expected type [...CustomEntity (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value element [2400] did not match expected type [...CustomEntity (n/a)]
[...]
Caused by: java.lang.IllegalArgumentException: Parameter value element [2400] did not match expected type [...CustomEntity (n/a)]
From the error message, I understand that the input should be a list of CustomEntity objects, instead of IDs (Long).
A potential workaround seems to be querying the custom_entities table in order to convert the custom entity ids from Long to CustomEntity objects.
However, that would require adding a JPA CustomEntityRepository, and seems very inefficient. It would require an additional database call just for the purpose of converting IDs into objects, so that I can eventually query for the IDs (which I already have had to begin with) in the my_entities table.
The reason why I have the IDs for the custom entities, but not the objects, is that they are provided by user input further up the line.
My question is thus: how can I implement a method in the MyEntityRepository that implements the MySQL query stated above based on a list of custom entity IDs, without converting them from Long into CustomEntity objects?
Or is there a more fundamental flaw in my design?
The query should end in "and e.customEntity.id in :ids" not in "and e.customEntity in :ids".
In my software, I have an entity (let's call it Member) with a collection of another entity (let's call it State). The query I need to write should return all members who have no State with a specific property value (e. g. 5).
Here are the relevant parts of the entities:
public class Member {
#JoinColumn(name = "MEMBER_ID")
#OneToMany
private List<State> states;
#Column
private String name;
}
public class State {
#Column
private int property;
}
Note that there is no bidirectional mapping between Member and State, the mapping is declared on the non-owning side of the relation (Member). In SQL I would create a query like this:
SELECT m.name
FROM Member m
WHERE NOT EXISTS (
SELECT *
FROM State s
WHERE
m.id = s.member_id
AND s.property = 5
);
But I don't know of any way to achieve the same thing in JPQL without having a mapping on the owning side of the relation. Is there any way to achieve this without having to bother with bidirectional mappings?
JPA allows to use collection references in subquery from clauses, so you can use this:
SELECT m.name
FROM Member m
WHERE NOT EXISTS(
SELECT 1
FROM m.states s
WHERE s.property = 5
)
This will produce exactly the SQL you want.
you can write native query like this
select name from Member where member_id not in ( select id from states
where property = 5)
Try something like this
select m.name from Member m where not exists(
from Member m1 join m1.states s where s.property = 5
)
or
select m.name from Member m where m.id not in(
select m1.id from Member m1 join m1.states s where s.property = 5
)
I don't want to hardcode constant values, I would rather specify them through a reference variable.
For example, rather then writing the next query:
#Query(value = "SELECT u FROM UserModel u WHERE u.status = 1")
..I would like to extract the hardcoded value '1' and write something like:
#Query(value = "SELECT u FROM UserModel u WHERE u.status = UserModel.STATUS_ACTIVE") //doesn't compile
Is there a way to specify constants like in the second example inside spring-data queries?
You have to use fully qualified class name like this:
#Query("SELECT u FROM UserModel u WHERE u.status = com.example.package.UserModel.STATUS_ACTIVE")
The bad thing about it though is that an IDE would not recognise this as an usage of the class UserModel. The only advantage is that you can keep the value in one place, which is sufficient most of the time. This has been resolved in IntelliJ IDEA 2017.1. I don't know about other IDEs.
I would recommend creating an Enum and a field of that enum on the entity.
public enum UserModelStatus{
ACTIVE, INACTIVE
}
public UserModel{
/* Other fields ommitted */
#Enumerated(EnumType.STRING)
private UserModelStatus status;
/* Get/Set Method */
}
Then create your repository method:
#Repository
public interface UserModelRepository extends JpaRepository<UserModel, Long>{
public List<UserModel> findByStatus(UserModelStatus status);
}
Using Spring Data you won't even need to write JPQL just call the method like:
#Autowired
UserModelRepository userModelRepository;
public void someMethod(){
List<UserModel> userModels = userModelRepository.findByStatus(UserModelStatus.ACTIVE);
}
Use as follows:
In the repository interface, define a constant as follows:
public static final String USER_QUERY = "SELECT u FROM UserModel u WHERE u.status = " + UserModel.STATUS_ACTIVE;
Now you can use
#Query(value=USER_QUERY)
I've managed to use class String constant in query via SpEL T() operator, which gives you access to static methods and constants on a given class. For String I have to wrap expression with single quotes ('), probably it will be needed for you as well (if QuerySyntaxException occurs).
Try something like this,
#Query("SELECT u FROM #{#entityName} u " +
"WHERE u.status = #{T(fully.qualified.path.UserModel).STATUS_ACTIVE}")
Note: somehow it doesn't work if you use UserModel instead of #{#entityName}.
In docs its mentioned briefly, see: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions-beandef-xml-based
Don't know since when this is supported, I've got spring-data-jpa 1.4.3, spring-framework 3.2.17
The answer to this seems to be 'No' for a standard solution.
Some JPA implementations may have solutions of their own but hibernate for one does not seem to and does not seem to support any of the methods suggested by other answers here.
When you want to use constants directly inside your #Query annotation you can write something like:
#Query("SELECT u FROM UserModel u WHERE u.status = " + UserModel.STATUS_ACTIVE)
Yes, this is a more elegant way and it is readable too. For example:
#Query("SELECT xyz.id FROM XYZ AS xyz WHERE xyz.status = " + Status.OPEN + " AND xyz.active=1")
Here, the Status is a simple class containing static constants:
public class Status {
public static final int OPEN = 1;
}
I have the following relation of three classes:
#Entity
public class User{
#OnetoMany
List<Attribute> attributes = new ArrayList<Attribute>();
}
#Entity
public class Attribute{
#ManyToOne
AttributeType attributeType;
}
#Entity
public class AttributeType{
#Column
String type;
}
One user can have n attributes of m types.
I need to create HQL query which will return all Atribute Types List<AttributeType> of specific user attributes.
For example user has attribute a of type t, atribute b of type t and attribute c of type t1.
I need to return List<AttributeType> which will contain t and t1.
Please help. I just got lost in this query.
You shall map Attribute to User many to one relation, so the following query is what you need:
select distinct atr.attributeType
from Attribute atr
where atr.user = :user
I think the following query will work too:
select distinct atrs.attributeType
from User as user
join user.attributes as atrs
where user.id = :user_id
using a CRUD and want o access a certain value in the databse. what I tried is:
Query query = em.createNativeQuery("select o from Table o where o.IDfield= '938'");
Table o = (Table)query.getSingleResult();
but this fails.
Any suggestions?
Your select looks like a jpql query so it won't work when passed directly to the database as a native query. You can use the createQuery method instead. Even better would be to use a named parameterized query declared on your entity like this:
#Entity
#NamedQueries({#NamedQuery(name="MyEntity.findById", query="select o from MyEntity o where o.IDfield = :id"})
public class MyEntity {
#Id
private String IDfield;
}
...
MyEntity entity = (MyEntity)em.createNamedQuery("MyEntity.findById").setParameter("id", "938").getSingleResult();