Can we pass or input a .sql file to the Query annotation in Spring JPA?
Something like this
#Query(file:/sql/employeeList.sql , nativeQuery=true)
/sql/employeeList.sql - i would like to put this file somewhere in the code, which will have the select sql in it.
I am not looking to have native sql string
#Query("select abc....", nativeQuery=true) -- I dont want to use like this. because my sql is too huge.
#Jens Schauder is right you can't specify a sql file in Spring Data Jpa but as an alternative solution you could use this library https://github.com/VEINHORN/spring-data-sqlfile or you could simply load the file on your own.
Note: im using lombok on this example
#Component
#Slf4j
#AllArgsConstructor
public class ArticleBatchRepository {
#PersistenceContext
EntityManager em;
public List<ArticleBatch> findAll(String articleId) {
Query nativeQuery = em.createNativeQuery(loadQuery("/db/queries/articles/BatchByWarehouse.sql"), ArticleBatch.class);
nativeQuery.setParameter(1, articleId);
return (List<ArticleBatch>) nativeQuery.getResultList();
}
private String loadQuery(String path) {
try {
InputStream is = this.getClass().getResourceAsStream(path);
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
} catch (Exception e) {
log.error("Failed to load query {}: {}", path, e.getMessage(), e);
return null;
}
}
}
You can't specify a sql file but Spring Data JPA will look in /META-INF/jpa-named-queries.properties
to find named queries, which allows you to specify queries in a sql file.
This is described in this answer to a similar question.
You can check this repository.
You need to provide a JpaQueryMethodFactory bean to your configuration. You can extend the JpaQueryMethod class and override the getAnnotatedQuery and getRequiredAnnotatedQuery functions to read queries from the file.
Related
I have been very impressed with Javers. Unfortunately, I am currently on a project that features poorly designed legacy code. I seek guidance in the application of JaversAuditable, JaversAuditableDelete, and JaversAuditableConditionalDelete annotations under the following scenarios:
Suppose my DAOs are using hibernate's update and merge methods. Should I adorn these DAO methods with any of the above annotations?
Suppose now that I have malformed DAO methods like the following:
public Group createNewGroup(String title, String desc, String color, int type) {
Group group = new Group();
group.setGroupTitle(title);
group.setGroupDesc(desc);
group.setGroupColor(color);
group.setType(type);
group.setCategory(Group.CATEGORY_GROUP);
getHibernateTemplate().save(group);
return group;
}
or
public void updateAlerts(Long userId) {
final String hqlUpdate = "update Alert a set a.acknowleged = true where a.systemUser.systemUserId = ?0";
try {
getHibernateTemplate().bulkUpdate(hqlUpdate, userId);
} catch(Exception e){
throw new RuntimeException(e);
}
}
are any of the annotations applicable to these methods?
I expect from examination of the JaversCommitAdvice class that the aforementioned annotations will not work. I would like to know what I can do short of manually calling Javers.commit() in each of these malformed DAO methods.
Use case: Connecting MySQL and oracle database
Issue: If I annotate any one of the data sources as primary, it always uses the primary database Identifier processing and forms the query based on that.
MySQL
#Bean
#Primary
#Qualifier("mySqlJdbcConverter")
public JdbcConverter mySqlJdbcConverter(JdbcMappingContext mappingContext, #Lazy RelationResolver relationResolver,
#Qualifier("mysqlJdbcOperationsReference") NamedParameterJdbcOperations mysqlJdbcOperationsReference) {
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(
mysqlJdbcOperationsReference.getJdbcOperations());
return new BasicJdbcConverter(mappingContext, relationResolver, mySqlJdbcCustomConversions(), jdbcTypeFactory,
IdentifierProcessing.create(new Quoting("`"), LetterCasing.UPPER_CASE));
}
#Bean
#Primary
#Qualifier("mySqlJdbcDialect")
public Dialect mySqlJdbcDialect(final JdbcConverter JdbcConverter) {
return MySqlDialect.INSTANCE;
}
Oracle
#Bean
#Qualifier("oracleJdbcConverter")
public JdbcConverter oracleJdbcConverter(JdbcMappingContext mappingContext, #Lazy RelationResolver relationResolver,
#Qualifier("oracleJdbcOperationsReference") NamedParameterJdbcOperations oracleJdbcOperationsReference) {
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(
oracleJdbcOperationsReference.getJdbcOperations());
return new BasicJdbcConverter(mappingContext, relationResolver, oracleJdbcCustomConversions(), jdbcTypeFactory,
IdentifierProcessing.create(new Quoting("\""), LetterCasing.UPPER_CASE));
}
#Bean
#Qualifier("oracleJdbcDialect")
#RequestScope
public Dialect oracleJdbcDialect(final JdbcMappingContext JdbcConverter) {
return OracleDialect.INSTANCE;
}
In the above case, always query carries backquote character. Even though it is connecting to the oracle database, but the identifier is always backquote
Query:
SELECT `service`.`SERVICE_ID` AS `SERVICE_ID`, `service`.`SERVICE_NAME` AS `SERVICE_NAME` FROM `service`
May I know why it is happening?
The Dialect is not picked up as bean from the ApplicationContext. If you want to use your own Dialect you need to do the following:
implement your own Dialect.
implement a JdbcDialectProvider returning that Dialect.
register the provider by putting a file spring.factories in the META-INF folder of your class path and add the line org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider=<fully qualified name of your JdbcDialectProvider>
See https://spring.io/blog/2020/05/20/migrating-to-spring-data-jdbc-2-0#dialects
But really you shouldn't have to do that, since dialects for Oracle and MySql are already provided out of the box.
I'm working on a Spring Boot 2.4.2 based project and using "spring-boot-starter-jdbc" and "com.oracle.database.jdbc" for Oracle Jdbc driver.
As I use JdbcTemplate to interact with DB, everything seems clean and easy. But I may need to support multiple database types in future - Oracle, SQL Server, MySQL, DB2, etc.
Did quite a bit of Googling but did not find any option for this..
Like mentioned above, I am using Spring-Jdbc (not Spring Data JDBC or Spring Data JPA) - how do I provide the SQL queries specific to each database supported in the code or configuration?
Please, let me know your thoughts. Thanks.
I'm not familiar with Spring JDBC, but you could user Spring dependency injection mechanism to create a profile for each database.
First an interface:
public interface DbQueries {
String createQueryForSelectingUsers();
}
Then implement the interface for each supported database:
#Profile("mysql")
#Component
public class MySqlDbQueries implements DbQueries {
#Override
public String createQueryForSelectingUsers() {
return "SELECT * FROM USER";
}
}
2nd example:
#Profile("oracle")
#Component
public class OracleDbQueries implements DbQueries {
#Override
public String createQueryForSelectingUsers() {
return "SELECT * FROM USER";
}
}
Afterwards use it where you need it:
public class MyRepository {
private DbQueries dbQueries;
// DbQueries implementation will be injected based on your current profile
public MyRepository(DbQueries dbQueries) {
this.dbQueries = dbQueries;
}
public void printAllUsers() {
String query = dbQueries.createQueryForSelectingUsers();
// stuff to execute query
}
}
Remember to start your app with a profile with e.q. --spring.profiles.active=mysql or adding active profile information into application.properties:
spring.profiles.active=mysql
I have a Spring Boot project which has multiple Model class
I want to use multiple model classes in my DAO implementation.
Can someone please explain how can I use HQL queries here.
Most of the examples i saw uses CRUDRepository. But in my understanding this limits us to one model class (please correct me if my understanding is wrong).
Secondly using CRUDRepository limits me to very specific methods. But HQL queries I need could be more complex like:
Query query = session.createQuery(from Employee where name=:name and place=:place and phone=:phone and gender=:gender);
Is it standard practice to use CRUDRepository even for HQL queries like this.
Otherwise how can I use such HQL queries. (If possible please also add what all dependencies and annotations i will need to add). I am using Hibernate and postgres.
If you wish to execute dynamic HQL queries without using repositories, etc, this works for me really nicely:
#Autowired
EntityManager entityManager;
#RequestMapping("/query")
#ResponseBody
public String testQuery() {
Query query = entityManager.createQuery("select u from User u");
List<User> users = query.getResultList();
users.forEach(u -> System.out.println(u.getFirstname()));
return "See Console";
}
The nice thing about it is that if you're quickly wanting to test a query, and you're using something like Spring LiveReload, JRebel, DCEVM or HotSwap with Hybris, you can change the query and save and refresh.
CRUD repositories are the standard way to write a DAO. If you look at the documentations, you will see that you can have complex queries that uses the fields of the entity (which can be pretty complex, I encourage you to look here at the query methods part - 4.3), and if you want even more complex methods, you can use the annotation #Query over a method:
#Repository
interface MyRepo implements JpaRepository<MyEntity,Long> {
#Query("your custom query here")
MyEntity findByFeature(String a)
}
Now you can insert you query and access you String a inside the query (like Select u where u.name="blabla" where blabla is your input string)
The #Query comes with the same dependency as the repositories (I think that spring-boot-starter-data-jpa is enough)
JpaRepository is surely awesome to use, but for someone who has to use complex HQL queries (due to your organization's recommendations etc), here is how i did it:
Get Session factory by injecting following bean in your configuration:
#Bean
public SessionFactory sessionFactory(#Qualifier("entityManagerFactory") EntityManagerFactory emf)
{
return emf.unwrap(SessionFactory.class);
}
Add EntityScan and ComponentScan to your configuration class
#EntityScan(basePackages = { "com.app.persistence" })
#ComponentScan(basePackages = { "com.app" })
Go ahead and use any HQL queries:
#Autowired
SessionFactory factory;
#Override
public String check() {
Session session = null;
try {
session = factory.openSession();
Query query = session.createQuery("from Test");
List<Test> res = query.list();
Test sing = res.get(0);
return sing.getName();
} catch (Exception e) {
System.out.println("Exception in Dao");
e.printStackTrace();
throw e;
} finally {
if (session != null) {
session.close();
}
}
}
Note: I am assuming that other things like configuring DataSource and all have been taken care already.
Anyone can feel free to correct me if my understanding or way was wrong.
But it worked well for me this way!! :)
I try to establish a deleteAll function which deletes all documents which are associated with a given repository and class. In order to this I created a custom N1ql query. But I want the Couchbase index to be updated before later database operations take place. My guess is that I have to change the consistency level of the query to achieve this behaviour.
Here and here I found some examples which do this by using the CouchbaseTemplate. But my template is null. Could anybody tell me what I am doing wrong?
public void deleteAll() throws DBException {
CouchbaseOperations couchbaseTemplate;
try {
couchbaseTemplate = templateProvider.resolve(getRepository().getClass(), getClassName().getClass());
} catch (Exception e) {
throw new DBException("Could not get couchbase client", e);
}
String statement = String.format("DELETE FROM %s WHERE _class='%s'",
couchbaseTemplate.getCouchbaseBucket().name(), getClassName());
ScanConsistency consistency = couchbaseTemplate.getDefaultConsistency().n1qlConsistency();
N1qlParams queryParams = N1qlParams.build().consistency(consistency);
N1qlQuery query = N1qlQuery.simple(statement, queryParams);
N1qlQueryResult result = couchbaseTemplate.queryN1QL(query);
//Result handling
}
}
The templateProvider is autowired.
It is not entirely clear about your repository and entity from your code snippet. Which version of SDC are you using?
If you are using operation mapping bean, you get the underlying couchbase template for the particular repository and entity using
#Repository
public interface MyRepository extends CrudRepository<MyEntity, String> {
}
public class MyService {
#Autowired
MyRepository repo;
#Autowired
RepositoryOperationsMapping templateProvider;
....
CouchbaseOperations operations = templateProvider.resolve(repo.getClass(),MyEntity.class);
Make sure to enable couchbase repositories with #EnableCouchbaseRepositories. If your repositories only use couchbase, you can also get the couchbase template bean directly.
#Autowired
CouchbaseTemplate template;