I'm using Java - Ibatis and mySQL with Flex/Flash on the front-end. I have one requirement that is to be able to dynamically add creterias and table to a query depending on the user role. here is an example
Same object calling same SQL but different result based on role
Role 1 : Fully Access to employees
SELECT *
FROM Employee A
Role 2 : Limited access to employees
SELECT *
FROM Employee A
, SECURE_LIST B
WHERE B.EmployeeID = A.EmployeeID
AND B.ROLE_ID = 'ROLE'
I could use Dynamic SQL
SELECT *
FROM Employee A
<isNotEmpty property="ROLE" >
, SECURE_LIST B
WHERE B.EmployeeID = A.EmployeeID
AND B.ROLE_ID = #ROLE#
</isNotEmpty>
Other ideas?
SELECT *
FROM Employee A
<isNotEmpty property="ROLE" >
inner join SECURE_LIST B on B.EmployeeID = A.EmployeeID
</isNotEmpty>
<dynamic prepend="WHERE">
<isNotEmpty property="ROLE" prepend="AND">
B.ROLE_ID = #ROLE#
</isNotEmpty>
</dynamic>
A little simpler than creating DAOs but still providing you the flexibility to add other joins or other where clause elements without having to include role in every parameter map
The problem with using role within the query is that you have to then supply it as an argument to the query for possibly every query. What happens when you need to supply arguments to the query? You'll need to add role to those parameter classes/maps too. It's all a bit messy.
I'd take a step back and define your DAO:
public interface MyDAO {
List<Employee> getEmployees();
...
}
and then create two implementations:
public class MyDAOSuper implements MyDAO {
public List<Employee> getEmployees() {
// call a query using your first SQL
}
}
public class MyDAOLimited implements MyDAO {
public List<Employee> getEmployees() {
// limited version
}
}
One advantage of this approach is that if certain methods shouldn't be used by a particular role you have the option of throwing some security violation exception.
Now how you plug that in to the rest of your application is something I don't have enough detail to comment on. You might be using BlazeDS in which case I'd suggest using the Spring integration with BlazeDS, which will open up dependency injection as an option.
Alternatively you could use a simple factory method (based on role) to get the correct DAO.
There are no doubt other ways to plug this in depending on your configuration. I think the above is a lot cleaner than what you're proposing though.
Related
I'm trying to utilize a registered function from my custom hibernate dialect inside of a formula field. The problem is that the formula field does not utilize my registered function. I'm trying to figure out what I'm doing wrong.
For background, I have an application that I'm working to make functional for both Oracle and Postgresql. Not simultaneously, but for whichever database its being deployed to. I have several formula fields in my models that are used to aggregate the names of OneToMany mapped entities into a single comma-delimited list for easy searching and display. This was done utilizing LISTAGG when it was purely Oracle. That won't work in Postgresql, but given that it needs to work with both environments, I can't just change the syntax of my Formula to STRING_AGG. So, I'm attempting to register a function for both that will utilize the appropriate format for whichever database is being used.
I'm using Custom Dialect extensions and registering my functions, but it doesn't utilize my registered function. I'm not sure what I'm doing wrong.
If this isn't actually possible and I'm approaching this from the wrong direction, is there a good approach to defining the formula fields dynamically? Not during runtime, but during compile time when the dialect is set?
public class CustomPostgresqlDialect extends PostgreSQL95Dialect {
public CustomPostgresqlDialect() {
super();
registerFunction("MY_LISTAGG", new SQLFunctionTemplate( StandardBasicTypes.STRING, " STRING_AGG(?1 , ', ' ORDER BY ?1) "));
}
}
...
public class CustomOracleDialect extends Oracle12cDialect {
public CustomOracleDialect() {
super();
registerKeyword("WITHIN");
registerFunction("MY_LISTAGG", new SQLFunctionTemplate( StandardBasicTypes.STRING,"LISTAGG(?1,', ') WITHIN GROUP(ORDER BY ?1)"));
}
}
And here is my model with the formula:
public class Contractor extends Object implements Serializable {
...
#OneToMany(mappedBy = "contractor", fetch = FetchType.LAZY)
private Set<ProjectManager> projectManagers;
...
#Formula("(" +
"SELECT\r\n" +
" MY_LISTAGG(PM.NAME)\r\n" +
"FROM\r\n" +
" PROJECTMANAGERS PM\r\n" +
" INNER JOIN CONTRACTORS C ON C.ID = PM.FK_CONTRACTOR\r\n" +
"WHERE\r\n" +
" C.ID = id" +
")"
)
#NotAudited
private String pmNames;
...
}
Like SternK wrote, it's not possible to use JPQL/HQL functions in #Formula and I would also advise against using subqueries in formulas in general as that will incur the penalty of always executing these subqueries even if you don't need the data.
I think this is a perfect use case for Blaze-Persistence Entity Views.
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.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Contractor.class)
public interface ContractorDto {
#IdMapping
Long getId();
#Mapping(value = "projectManagers.name", fetch = MULTISET)
Set<String> getPmNames();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ContractorDto a = entityViewManager.find(entityManager, ContractorDto.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<ContractorDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary! So in this case, a SQL query like the following would be created:
select
c.id,
(
select json_agg(json_object(c1, pm.name))
from project_manager pm
where pm.fk_contractor = c.id
)
from contractor c
If you really want an aggregated string, you could also use the GROUP_CONCAT function as provided by Blaze-Persistence within the mapping:
#EntityView(Contractor.class)
public interface ContractorDto {
#IdMapping
Long getId();
#Mapping("GROUP_CONCAT(projectManagers.name, 'SEPARATOR', ', ', 'ORDER BY', projectManagers.name,, 'ASC')")
String getPmNames();
}
You can not use #Formula for this purpose. As it's stated in the documentation:
You should be aware that the #Formula annotation takes a native SQL clause which may affect database portability.
You can try to use JPQL/Criteria query for this.
I'm learning Spring MVC and I want find a car via an id but get in return the name.
In my service class I call a generic method getXXXById. This is something JPA gives me by nature.
I know that I get the whole entity but how can I just receive the corresponding name to the id.
Example: I call getCarById(2) and it gives me back Tesla.
My Table:
id | Name
----------
1 | Ford
2 | Tesla
My Service:
class CarService {
// code ...
public Optional<CarEntity> getCarById(int id) {
return carRepository.findById(id);
}
There are two options to do that.
Making your own query
You could write your own query in JQPL to retrive only names.
For example you could create method like that in your repository.
#Query("select t.name from CarEntity where id = ?1")
public String findNameById(Integer id);
more information on this feature of Spring Data Jpa HERE
Projections
Second option is to make projection. As it is written in documentation
Spring Data query methods usually return one or multiple instances of the aggregate root managed by the repository. However, it might sometimes be desirable to rather project on certain attributes of those types. Spring Data allows to model dedicated return types to more selectively retrieve partial views onto the managed aggregates.
In simple words, it allows you to aggregate your results form queries in some limited set of attributes rather then whole entity.
Specifically for your needs I'd suggest to use first approch, but it is worth to know both.
I just got started with Spring JPA and I am reviewing code written by my coworker.
I see that he has been using the following code to find a Login object using username:
public interface LoginDao extends JpaRepository<Login, Long> {
#Query("SELECT u FROM Login u WHERE LOWER(u.username) = LOWER(:username)")
Login findByUsername(#Param("username") String username);
}
Cant he just create a method like this:
#GET
#Path("{username}")
public Login getOne(#PathParam("username") String username) {
Login login = loginDao.findOne(username);
if (login == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
return login;
}
}
What are the fundamental advantages of using #Query rather than writing a method approach. Or am I plain wrong and both have different purposes.
I personally dont want to write queries inside the code. I think mixing java with sql queries can make code look uglier.
Our stack is java, JPA, Jersey, JAX-RS, Postgreql, Spring Boot, Hibernate
Regards
First, This is not an SQL query, this is a JPQL one. It would be a native SQL query if the nativeQuery attribute was set to true, which is not the case by default.
It is perfectly fine to create queries with JPQL, you will be able to switch from Hibernate to Eclipse Link or another JPA implementation without issues. You should also be able to switch from Postgres to MySQL, SQL Server...
You have to start to worry if your coworker creates queries with #Query(nativeQuery = true, value = "..."), otherwise it looks fine to me.
Second, when you look to your repository declaration, you can see that the ID field for your Login entity is of type Long. That means the loginDao.findOne(..) method wants a Long parameter. Probably a surrogate key which is not username.
This is why your coworker created this new method: to find a Login row by something else than the Long surrogate key. (Here by username which is most likely a business key, but I do not know your application.)
Just for your information: Spring automatically creates queries with the signature of your repository methods (derived queries). For example:
public interface LoginDao extends JpaRepository<Login, Long> {
Login findByUsername(String username);
}
Spring JPA will automatically create a query looking for a field named username and create a WHERE clause with it.
Notice that it is not the same than your coworker query because it will not use the LOWER function.
Some anchors in the JPA documentation about those derived queries:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-keywords
Another thing about your title:
"Why need #Query when I can write a method in Spring JPA"
Well this #Query writes a method in Spring JPA. :^)
JPARepository has come up with some of the Supported keywords which will write the queries themselves based on your entity.
If we are looking something out of box from what JPARepository provides #Query is useful like - Joining multiple queries, writing native queries etc.
From your code snippet both would do the same
For more info you can refer - https://docs.spring.io/spring-data/jpa/docs/1.4.2.RELEASE/reference/html/jpa.repositories.html
I need to set a table name dynamically so that I use query.setText(tname,abc)
e.g: select a.name from :tname where a.id = '2'
I used setText() because when I use setString() it says "tname is a invalid parameter" because I assume that Hibernate adds '' when setting string parameters.
But even setText() does not help and gives the same exception.
How can I set the table name dynamically?
Reply to PSR:
So you mean replace table name as a java string replacement. But then we can not take support of sql injections prevention etc from hibernate right? Also How we bind parameters in hibernate in a situation where like statement,
Eg: name like "%:name%"
This also gives me Illegal argument exception: Parameter does not exist as a named parameter when i try to bind it using query.setString(name,"def");
Hibernate will not do this for you, because it works with PreparedStatements, and you can't prepare a statement where the table being queried isn't known yet.
I don't see why you would be exposing table names to end users, so preventing SQL injection doing a regular string substitution should be easy. You use some sort of business logic to determine the correct table from a list that only you know. The table name isn't coming from user input at all.
Depending on your choice of RDBMS, you may find a discriminator column, or table inheritance with partitioning to be a better way of handling a situation where identical queries are made against different tables.
It is not possible to set table name dynamically.You can set dynamically column names.it is not possible to set table name
try like this
select a.name from '+table name+'where a.id = '2'
In my opinion, There are 2 ways to resolve this issue:
1- If you are using Spring and Hibernate together, you could use SpEL and it would be like #{#entityName} as it is described here
#Entity
public class User {
#Id
#GeneratedValue
Long id;
String lastname;
}
public interface UserRepository extends JpaRepository<User,Long> {
#Query("select u from #{#entityName} u where u.lastname = ?1")
List<User> findByLastname(String lastname);
}
2-You could use CriteriaBuilder like
CriteriaQuery<YourEntity> cr = cb.createQuery(YourEntity.class);
Root<YourEntity> root = cr.from(YourEntity.class);
cr.select(root);
I copied the source codes from the provided links and they are described there much better
I would like in HQL to use the result of a abstract method in my "where" clause. Can this be done?
Something like this:
#NamedQuery(name="getMailRelations", query="Select mr from MailRelation mr where mr.getOccurrences() > 5"
I have a mapped super class something like this
#Entity
#Table(name="Mail_Entity", schema="relations")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="relationType", discriminatorType=DiscriminatorType.STRING)
#PersistenceContext(name="domas")
public abstract class MailRelation {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST})
#JoinColumn(name="mailId", referencedColumnName="mailId", nullable=false)
private Mail mail;
public void setMail(Mail mail) {
this.mail = mail;
if(!mail.getMailRelations().contains(this))
mail.addMailRelation(this);
}
public abstract int getOccurrences();
public Mail getMail() {
return mail;
// and more code......
No, that is impossible. The HQL code is translated into SQL and executed on the database. Generally Java methods can't be translated into SQL, and the database does not have any access to your Java code.
If you have a problem like this, there are for example these three possibilities to handle it. None of these possibilities is perfect.
1) You write the logic of the method in HQL (or SQL) using WHERE, GROUP BY and HAVING. In your example the getOccurrences() method seems to return a number of rows, which perhaps can be handled by `HAVING COUNT(...) > 5'.
2) You use database stored procedures. These are p. ex. procedures written in PL/SQL (in the case of Oracle). They can be accessed in select statements. But you loose the independency of the chosen database.
3) You load more rows than necessary and filter later in your Java code.
The solution is up to you, but I'm adding some additional options you can consider:
If you manage to precalculate the hash in all cases, use a parametrized named query:
#NamedQuery(name="getMailRelations", query="Select mr from MailRelation mr where :occurrences > 5"
then, you can call the query and add the parameter "occurrences":
String precalculatedHash = //your code here.
entityManager.createNamedQuery("getMailRelations",MailRelation.class).setParameter("occurrences", precalculatedHash).getResultList();
Another option is to go a little deeper with your hash logic, and determine what do you want to achieve with it. With that in mind you can use Criteria API to create a query and add all the restrictions represented by that hash. This can be a little tricky, so discard this option if the hash proves to be too context-depending (and I mean if it relies a lot on what do you have persisted, and the context of your application).
The third option is to bring all the results (or the smallest set of results possible, through either parameters or, again, Criteria API), and make your particular filtering logic.