I am trying to use querydsl for building dynamic queries for dynamic schemas. I am trying to get just the query instead of having to actually execute it.
So far I have faced two issues:
- The schema.table notation is absent. Instead I only get the table name.
- I have been able to get the query but it separates out the variables and puts '?' instead which is understandable. But I am wondering if there is some way to get fully materialized query including the parameters.
Here is my current attempt and result(I am using MySQLTemplates to create the configuration):
private SQLTemplates templates = new MySQLTemplates();
private Configuration configuration = new Configuration(templates);
String table = "sometable"
Path<Object> userPath = new PathImpl<Object>(Object.class, table);
StringPath usernamePath = Expressions.stringPath(userPath, "username");
NumberPath<Long> idPath = Expressions.numberPath(Long.class, userPath, "id");
SQLQuery sqlQuery = new SQLQuery(connection, configuration)
.from(userPath).where(idPath.eq(1l)).limit(10);
String query = sqlQuery.getSQL(usernamePath).getSQL();
return query;
And what I get is:
select sometable.username
from sometable
where sometable.id = ?
limit ?
What I wanted to get was:
select sometable.username
from someschema.sometable
where sometable.id = ?
limit ?
Update: I came up with this sort of hack to get parameters materialized(Not ideal and would love better solution) But still could not get Schema.Table notation to work:
Hack follows. Please suggest cleaner QueryDsl way of doing it:
String query = cleanQuery(sqlQuery.getSQL(usernamePath));
private String cleanQuery(SQLBindings bindings){
String query = bindings.getSQL();
for (Object binding : bindings.getBindings()) {
query = query.replaceFirst("\\?", binding.toString());
}
return query;
}
To enable schema printing use the following pattern
SQLTemplates templates = MySQLTemplates.builder()
.printSchema()
.build();
SQLTemplates subclasses were used before, but since some time the builder pattern is the official way to customize the templates http://www.querydsl.com/static/querydsl/3.3.1/reference/html/ch02s03.html#d0e904
And to enable direct serialization of literals use
//configuration level
configuration.setUseLiterals(true);
//query level
configuration.setUseLiterals(true);
Here is a full example
// configuration
SQLTemplates templates = MySQLTemplates.builder()
.printSchema()
.build();
Configuration configuration = new Configuration(templates);
// querying
SQLQuery sqlQuery = new SQLQuery(connection, configuration)
.from(userPath).where(idPath.eq(1l)).limit(10);
sqlQuery.setUseLiterals(true);
String query = sqlQuery.getSQL(usernamePath).getSQL();
If you always just want the SQL query string out, move setUseLiterals from query to configuration.
Concerning the usage of Querydsl expressions the usage of code generation like documented here is advised http://www.querydsl.com/static/querydsl/3.3.1/reference/html/ch02s03.html
It will make your code typesafe, compact and readable.
If you want to try Querydsl without code generation you can replace
Path<Object> userPath = new PathImpl<Object>(Object.class, variable);
with
Path<Object> userPath = new RelationalPathBase<Object>(Object.class, variable, schema, table);
When working with QueryDSL, you must provide a template for the database platform to build the query for. I see you are already are doing this here:
private SQLTemplates templates = new MySQLTemplates();
private Configuration configuration = new Configuration(templates);
To make the schema name appear in the generated query, the only way I have found to do this is (there may be an easier way) is to extend the template class and explicitly call this.setPrintSchema(true); inside the constructor. Here is a class that should work for MySql:
import com.mysema.query.sql.MySQLTemplates;
public class NewMySqlTemplates extends MySQLTemplates {
public NewMySqlTemplates() {
super('\\', false);
}
public NewMySqlTemplates(boolean quote) {
super('\\', quote);
}
public NewMySqlTemplates(char escape, boolean quote) {
super(escape, quote);
this.setPrintSchema(true);
}
}
Then simply use this NewMySqlTemplates class in place of the MySQLTemplates class like this:
private SQLTemplates templates = new NewMySQLTemplates();
private Configuration configuration = new Configuration(templates);
I have this working using PostgresTemplates, so I may have a typo or mistake in the NewMySqlTemplates class above, but you should be able to get it to work. Good luck!
Related
I am trying to find how to use mongo Atlas search indexes, from java application, which is using spring-data-mongodb to query the data, can anyone share an example for it
what i found was as code as below, but that is used for MongoDB Text search, though it is working, but not sure whether it is using Atlas search defined index.
TextQuery textQuery = TextQuery.queryText(new TextCriteria().matchingAny(text)).sortByScore();
textQuery.fields().include("cast").include("title").include("id");
List<Movies> movies = mongoOperations
.find(textQuery, Movies.class);
I want smaple java code using spring-data-mongodb for below query:
[
{
$search: {
index: 'cast-fullplot',
text: {
query: 'sandeep',
path: {
'wildcard': '*'
}
}
}
}
]
It will be helpful if anyone can explain how MongoDB Text Search is different from Mongo Atlas Search and correct way of using Atalas Search with the help of java spring-data-mongodb.
How to code below with spring-data-mongodb:
Arrays.asList(new Document("$search",
new Document("index", "cast-fullplot")
.append("text",
new Document("query", "sandeep")
.append("path",
new Document("wildcard", "*")))),
new Document())
Yes, spring-data-mongo supports the aggregation pipeline, which you'll use to execute your query.
You need to define a document list, with the steps defined in your query, in the correct order. Atlas Search must be the first step in the pipeline, as it stands. You can translate your query to the aggregation pipeline using the Mongo Atlas interface, they have an option to export the pipeline array in the language of your choosing. Then, you just need to execute the query and map the list of responses to your entity class.
You can see an example below:
public class SearchRepositoryImpl implements SearchRepositoryCustom {
private final MongoClient mongoClient;
public SearchRepositoryImpl(MongoClient mongoClient) {
this.mongoClient = mongoClient;
}
#Override
public List<SearchEntity> searchByFilter(String text) {
// You can add codec configuration in your database object. This might be needed to map
// your object to the mongodb data
MongoDatabase database = mongoClient.getDatabase("aggregation");
MongoCollection<Document> collection = database.getCollection("restaurants");
List<Document> pipeline = List.of(new Document("$search", new Document("index", "default2")
.append("text", new Document("query", "Many people").append("path", new Document("wildcard", "*")))));
List<SearchEntity> searchEntityList = new ArrayList<>();
collection.aggregate(pipeline, SearchEntity.class).forEach(searchEntityList::add);
return searchEntityList;
}
}
I have 2 database one is mysql and other is postgree.
I tried to get postgree data from mysql transactional method.
#Transactional(value = "pg")
public List<String> getSubordinate(){
Query q1 = JPA.em().createNativeQuery("select vrs.subordinate_number, vrs.superior_number\n" +
"from view_reporting_structure vrs\n" +
"where vrs.superior_number = :personel_number");
q1.setParameter("personel_number","524261");
List<String> me = q1.getResultList();
return me;
}
}
from another method
#Transactional
public Result getOpenRequestList(){
Subordinate subordinate = new Subordinate();
List<String> subordinateData = subordinate.getSubordinate();
....
}
i got error
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'db_hcm.view_reporting_structure' doesn't exist
so my Postgre method recognized as mySQL transaction which is the view not exist in mySQL database. how do I get data from different presistence unit with 1 method?
I never did it (different databases), but I guess the following may work.
For example, you have the following data source definition in application.conf:
# MySql
db.mysql.driver=com.mysql.jdbc.Driver
... the rest of setting for db.mysql
# H2
db.postgre.driver=org.postgresql.Driver
... the rest of setting for db.postgre
Instead of using #Transactional annotation, manage a transaction explicitly and use JPA withTransaction API:
private static final String MYSQL_DB = "mysql";
private static final String POSTGRE_DB = "postgre";
public List<String> getSubordinate() {
JPA.withTransaction(MYSQL_DB, true/* this is read-only flag*/,
() -> {
Query q1 = JPA.em().createNativeQuery("select vrs.subordinate_number, vrs.superior_number\n" +
"from view_reporting_structure vrs\n" +
"where vrs.superior_number = :personel_number");
q1.setParameter("personel_number","524261");
List<String> me = q1.getResultList();
return me;
}
}
public Result getOpenRequestList(){
JPA.withTransaction(POSTGRE_DB, true/* this is read-only flag*/,
() -> {
Subordinate subordinate = new Subordinate();
List<String> subordinateData = subordinate.getSubordinate();
....
}
}
Note: I prefer always use withTransaction, since it allows better control of unhappy flow. You should wrap the call with try-catch. If JPA throws a run-time exception on commit, you can do proper error handling. In case of using #Transactional annotation, commit takes place after controller have finished and you cannot handle the error.
I would like to avoid passing SPARQL queries around as Strings. Therefore I use Jena's API for creating my queries. Now I need a PropertyPath in my query, but I can't find any Java class supporting this. Can you give me a hint?
Here's some example code where I would like to insert this (Jena 3.0.1):
private Query buildQuery(final String propertyPath) {
ElementTriplesBlock triplesBlock = new ElementTriplesBlock();
triplesBlock.addTriple(
new Triple(NodeFactory.createURI(this.titleUri.toString()),
//How can I set a property path as predicate here?
NodeFactory.???,
NodeFactory.createVariable("o"))
);
final Query query = buildSelectQuery(triplesBlock);
return query;
}
private Query buildSelectQuery(final ElementTriplesBlock queryBlock) {
final Query query = new Query();
query.setQuerySelectType();
query.setQueryResultStar(true);
query.setDistinct(true);
query.setQueryPattern(queryBlock);
return query;
}
You can use PathFactory to create property paths
Consider the graph below:
#prefix dc: <http://purl.org/dc/elements/1.1/>.
#prefix ex: <http://example.com/>.
ex:Manager ex:homeOffice ex:HomeOffice
ex:HomeOffice dc:title "Home Office Title"
Suppose you want to create a pattern like:
?x ex:homeOffice/dc:title ?title
The code below achieves it:
//create the path
Path exhomeOffice = PathFactory.pathLink(NodeFactory.createURI("http://example.com/homeOffice"));
Path dcTitle = PathFactory.pathLink(NodeFactory.createURI("http://purl.org/dc/elements/1.1/title"));
Path fullPath = PathFactory.pathSeq(exhomeOffice,dcTitle);
TriplePath t = new TriplePath(Var.alloc("x"),fullPath,Var.alloc("title"));
Is there a way to build SQL in phases/stages using jOOQ? Something like:
DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
DSL dsl = create.from(table("links"));
if( !StringUtils.isEmpty(place) ) { // place is specified, change the query
long placeId = getPlaceId();
if (placeId > 0) {
dsl = create.from(table("place_links"))
.join(table("links"))
.on(field("links.id").equal(field("place_links.link_id")))
.where(field("place_links.place_id").equal(placeId));
}
}
String sql = dsl.select(field("*"))
.orderBy("links.score")
.limit(1)
.getSQL();
The above won't compile but I am looking for something on similar principles. I need to start with from since the target table changes at runtime.
The requirement is that the final query changes at runtime depending on values which are fed in.
SQL doesn't feel like a very composable language if you start constructing the SELECT statement right away. But if you think of the different clauses as being the dynamic building blocks, things immediately become a lot simpler. In your case:
Table<?> from = table("links");
Condition where = trueCondition();
if (!StringUtils.isEmpty(place)) {
long placeId = getPlaceId();
if (placeId > 0) {
from = from.join("place_links").on("links.id = place_links.link_id");
where = where.and("place_links.place_id = ?", placeId);
}
}
DSL.using(conn)
.selectFrom(from)
.where(where)
.orderBy(field("links.score"))
.limit(1)
.fetch();
The above is assuming this
import static org.jooq.impl.DSL.*;
More about how to build SQL statements dynamically with jOOQ is described here:
http://www.jooq.org/doc/latest/manual/sql-building/dynamic-sql
Does anybody know is it possible to write in JOOQ select with a set of predefined values? I need it for insert if not exists.
For example,
INSERT INTO test
(text)
SELECT '1234567890123456789'
WHERE
NOT EXISTS (
SELECT id FROM test WHERE text = '1234567890123456789'
);
I've found the answer by myself:
List<Param<?>> params = new LinkedList<>();
params.add(DSL.val("1234567890123456789"));
List<Field<?>> fields = new LinkedList<>();
fields.add(TEST.TEXT);
SelectConditionStep<Record1<TEXT>> notExistsSelect = context.select(TEST.TEXT).from(TEST).where(TEST.TEXT.eq("1234567890123456789"));
SelectConditionStep<Record> insertIntoSelect = context.select(params).whereNotExists(notExistsSelect);
context.insertInto(TEST, fields).select(insertIntoSelect).execute();
But it would be great, if we had an ability to do it via InsertQuery. I've not found the way to do it.