Multiple selection from different tables in one query - spring

My question is about optimizing queries with mybatis.
List<SomeObject> someObjectList = oracleMapper.getAllSomeObject();
in my project I have to unload data from one database and make a request to another
List<String> subscriptionIds = someObjectList.stream().map(g -> g.getSubscriptionId()).collect(Collectors.toList());
List<Subscription> subscriptionsByIds = postgresMapper.getSubscriptionsByIds(subscriptionIds);
List<SubscriptionSubject> subjectsByIds = postgresMapper.getSubscriptionSubjectsByIds(subscriptionIds);
I have to do a query using a list of id to different tables twice
<select id="getSubscriptionsByIds" resultType="com.mappers.Subscription">
SELECT "s_id" as sId, "old" as old FROM "subscriptions"."subscriptions" s where s."subscription_id_old" IN
<foreach item="item" collection="subscriptionIds" separator="," open="(" close=")">
#{item}
</foreach>
</select>
<select id="getSubscriptionSubjectsByIds" resultType="com.mappers.SubscriptionSubject">
SELECT "s_id" as sId, "old" as old FROM "subscriptions"."subscription_subjects" s where s."subscription_id_old" IN
<foreach item="item" collection="subscriptionIds" separator="," open="(" close=")">
#{item}
</foreach>
</select>
#Data
public class Subscription {
private Long subscriptionId;
private String subscriptionIdOld;
//others
}
#Data
public class SubscriptionSubject {
private Long subscriptionId;
private String subscriptionIdOld;
//others
}
is it possible somehow not to do two queries, but get by with one for two tables?
There is a lot of data and there will be a lot of requests too, I would like to reduce it. thanks in advance

If the only difference between both queries is the table name. You can pass that value as an extra parameter and add it to the query using "SQL Injection".
For example:
<select id="getSubscriptionsByIds" resultType="com.mappers.Subscription">
SELECT "s_id" as sId, "old" as old FROM ${tableName} s
WHERE s."subscription_id_old" IN
<foreach item="item" collection="subscriptionIds" separator=","
open="(" close=")">
#{item}
</foreach>
</select>
Make sure the object you are passing as a parameter includes an extra property tableName apart from the list of IDs you are already passing.
Notice that in order to implement SQL Injection you need to use ${parameter} instead of #{parameter}. Use SQL Injection when absolutely necessary only. In the wrong hands it can open security vulnerabilities in your application.

Related

Why can JPQLs modifying queries only return void or int?

When i want to modify the database via JPQL i have to mark the query as Transactional and Modiyfing. If i do so, the return type of the method representing the query has to be either void or int(representing the number of edited rows i think). Why are only the two return types allowed? If i do a HTTP-PUT request and update the object with an own JPQL query, i would like to return the updated object again. Whats the best way to do it if the return type of the query has to be void or int? Do i have to do a seperate query/request again which selects the object after it was updated?
EDIT:
Thats how i call the query:
if (inactivityListDTO.getProjectIds().size() > 0) {
projectRepository.updateProjectsIsArchivedByProjectIds(inactivityListDTO.getProjectIds(), inactivityListDTO.getIsArchived());
}
Thats the query:
#Transactional
#Modifying
#Query("UPDATE Project project SET project.isArchived = :isArchived,
project.archivedDate = current_date " +
"WHERE project.id IN :ids")
void updateProjectsIsArchivedByProjectIds(#Param("ids") List<Long> ids, #Param("isArchived") boolean isArchived);
Because it finally boils down to execute a standard UPDATE SQL in the DB , and the UPDATE in standard SQL only returns the number of records being updated and does not return a result set.
And yes , if you need get a record 's value after update , you have to query it again. Alternatively , you should consider using a JPA way to update a record , which first query the object , then update it by changing its state . Something like below (Assume you are using spring #Transactional to manage the transactional boundary):
#Transactional
public void changeEmployeeSalary(Integer employeeId , Integer salary){
Employee employee = entityManager.find(Employee.class , employeeId);
employee.setSalary(salary);
}
In this way , you do not need to query the record again after it is updated and you also do not need to manually write a UPDATE SQL.

MyBatis inserts one to many relationship

I have my very simple one (Question) to many (Options) relationship, Option has the FK (questionId) to Question, now I'm going to insert a Question object with a list of Options inside within one XML configuration.
Question:
public Class Question{
...
private Integer questionId;
private List<Option> options;
....
}
Option:
public Class Option{
...
private Integer optionId;
private String context;
private Integer questionId; //FK
....
}
The configuration:
...
<insert id="insertQuestion" parameterType="com.pojos.Question" useGeneratedKeys="true">
INSERT into question (...) VALUES (...);
<selectKey keyColumn="questionId" keyProperty="questionId" resultType="int">
SELECT MAX(questionId) FROM question;
</selectKey>
INSERT INTO option (context, questionId)
VALUES
<foreach collection="options" item="option" open="(" separator="),(" close=")">
#{option.context}, #{questionId}
</foreach>
</insert>
...
As you can see, I'd like to use the just auto-generated questionId as the FK info for the options, however it doesn't work through, how can I make it work? or I cannot put them into a single scope?
In java 8 you could use default interface methods.
First you need to separate the operation with something like this:
Main table:
INSERT into question (...) VALUES (...);
<selectKey keyColumn="questionId" keyProperty="questionId" resultType="int">
SELECT MAX(questionId) FROM question;
</selectKey>
One to many relationship
INSERT INTO option (context, questionId)
VALUES
<foreach collection="options" item="option" open="(" separator="),(" close=")">
#{option.context}, #{questionId}
</foreach>
Then in your interface you will have at least three methods: insertQuestion, insertQuestionOptions and the default method (you could call it insertQuestionWithOptions for example) which uses both methods to insert it.
public interface QuestionDao {
int insertQuestion(Question question);
int insertOptions(List<Option> options);
default int insertQuestionsWithOptions(Question question) {
insertQuestion(question);
insertOptions(question.getOptions());
}
}
So, now you can use the method insertQuestionsWithOptions(Question question) which will insert question and options one to many relationship.

JPA #OneToMany by default is not so Lazy and fetch everything

Current project runs on Spring + Openjpa + Roo. I have an entity like this
public class Zoo{
....
#OneToMany(mappedBy="zoo", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<Elephant> elephants;
#OneToMany(mappedBy="zoo", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<Jaguar> jaguars;
#OneToMany(mappedBy="zoo", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private List<Tiger> tigers;
....
}
Then I have a simple UI page just trying to update the Zoo name, however from SQL trace log after the simple query
SELECT t0.id, t0.name
FROM Zoo t0
WHERE t0.id = ?
there are a query like this
SELECT * FROM Zoo, Tiger, TigerProduct, TigerFood, FoodSupplier, SupplierContacts...
and a hundreds queries like this:
SELECT * FROM TigerProduct where tiger.id =: id_1
.....
SELECT * FROM TigerProduct where tiger.id =: id_n
....
....
SELECT * FROM TigerFood where tiger.id =: id_1
....
SELECT * FROM TigerFood where tiger.id =: id_n
And same to Jaguar and Elephant as well. This makes this simple action really slow when there is large amount of data resides in the database.
The java code for the first query and the ones after is pretty simple:
public static Zoo findZoo(Long id) {
if (id == null) return null;
return entityManager().find(Zoo.class, id);
}
from above it looks like the default FetchType.Lazy on #OneToMany relation is not so lazy at all that JPA tries to pull all data on the chain.
So what's going on and how to clear this situation? I only prefer to have the first query and that's it
FetchType.Lazy is only a hint, and not a requirement, as the documentation says. So you cannot rely on this behavior, you can only hope that your JPA provider respects your hint. Also JPA does not forces a way how the JPQL queries or entitymanager calls are converted to SQL code, so it is somehow our duty to select a JPA provider+version that knows how to do things better (as we define what better means). This was probably a decision that should encourage the competition between JPA providers.

Adding Programmatically datasource / dataset to LocalReport

Is there any way to add programmatically a datasource / dataset to a Microsoft.Reporting.WebForms.LocalReport when the report-XmlFile (*.rdlc) has no datasource / dataset definitions at design-time?
This works if I already have a datasource / dataset definition in my *.rdlc
C#
public byte[] RenderReport(string reportName, string reportFormat)
{
LocalReport report = LoadReport(reportName);
//Has same name like DataSet in *.rdlc
ReportDataSource rds = new ReportDataSource("DataSet1", getData());
report.DataSources.Clear();
report.DataSources.Add(rds);
return report.Render(reportName);
}
private DataTable getData()
{
DataTable dt = new DataTable();
dt.Columns.Add(new DataColumn("ID",typeof(System.String)));
dt.Columns.Add(new DataColumn("NAME", typeof(System.String)));
dt.Rows.Add(new string[] { "1", "Me" });
return dt;
}
*.rdlc
<DataSources>
<DataSource Name="DataSource1">
<ConnectionProperties>
<DataProvider>System.Data.DataSet</DataProvider>
<ConnectString>/* Local Connection */</ConnectString>
</ConnectionProperties>
</DataSource>
</DataSources>
<DataSets>
<DataSet Name="DataSet1">
<Query>
<DataSourceName>DataSource1</DataSourceName>
<CommandText>/* Local Query */</CommandText>
</Query>
<Fields>
<Field Name="ID">
<DataField>ID</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
<Field Name="NAME">
<DataField>NAME</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
</Fields>
</DataSet>
</DataSets>
But if I remove the datasource / dataset definition I get
{Microsoft.Reporting.DefinitionInvalidException: The definition of the
report '' is invalid. --->
Microsoft.ReportingServices.ReportProcessing.ReportPublishingException:
The Value expression for the text box ‘Textbox1’ refers to the field
‘ID’. Report item expressions can only refer to fields within the
current dataset scope or, if inside an aggregate, the specified
dataset scope. Letters in the names of fields must use the correct
case.}
Do I always have to create something like a "Dummy"-DataSource/DataSet or do I miss something in my code?
I hope there is another solution as manipulating the XML before rendering-process, any ideas?
Thanks!
You can't leave RDLC witout DataSets, if you are using it and RDLC is embedded in your project.
Either you leave DataSet fixed and change only it's items either try to load report definition from XML
// Valid XML with dynamic DataSources and DataSets
string s = #"<?xml version=""1.0"" encoding=""utf-8""?><Report ...>...</Report>";
report.LoadReportDefinition(new MemoryStream(Encoding.UTF8.GetBytes(s)));
return report.Render(reportName);

HQL and Session.Query ignores eager fetching defined in mapping

I have a problem with NHibernate not using my mappings configuration for eager loading a collection when I get something using HQL or Linq (Session.Query). Session.Get and Session.QueryOver is working like expected.
I'm using NHibernate 3.2. Here's the mapping of a collection in my Product mapping.
<bag name="OrderItems" inverse="true" cascade="none" lazy="false" fetch="join">
<key column="order_id" />
<one-to-many class="OrderItem" />
</bag>
and from the other side the mapping looks like this:
<many-to-one name="Product" class="Product" column="product_id" not-null="true" />
I have 4 Tests, 2 are successfull and 2 are not. They use Session.SessionFactory.Statistics to keep track of CollectionFetchCount (was OrderItems selected in 1 joined query or in a separate). The intent is to have OrderItems selected and loaded when selecting the product as OrderItems are almost always accessed as well.
LastCreated is a simple reference to the last product inserted into the DB.
[Test] /* Success */
public void Accessing_Collection_Using_Session_Get_Results_In_1_Select()
{
// Get by Id
var product = Session.Get<Product>(LastCreated.Id);
var count = product.OrderItems.Count;
Assert.AreEqual(0,statistics.CollectionFetchCount,"Product collectionfetchcount using Get");
}
[Test] /* Success */
public void Accessing_Collection_Using_Session_QueryOver_Results_In_1_Select()
{
// Get by Id
var product = Session.QueryOver<Product>().SingleOrDefault();
var count = product.OrderItems.Count;
Assert.AreEqual(0, statistics.CollectionFetchCount, "Product collectionfetchcount using QueryOver");
}
[Test] /* Fail */
public void Accessing_Collection_Using_Session_Query_Results_In_1_Select()
{
// Get by IQueryable and Linq
var product = Session.Query<Product>().Single(x => x.Id == LastCreated.Id);
var count = product.OrderItems.Count;
Assert.AreEqual(0, statistics.CollectionFetchCount, "Product collectionfetchcount using Linq");
}
[Test] /* Fail */
public void Accessing_Collection_Using_HQL_Results_In_1_Select()
{
// Get by IQueryable and Linq
var product = Session.CreateQuery("from Product where Id = :id")
.SetParameter("id",LastCreated.Id)
.UniqueResult<Product>();
var count = product.OrderItems.Count;
Assert.AreEqual(0, statistics.CollectionFetchCount, "Product collectionfetchcount using HQL");
}
Is this intended behaviour or am I doing something wrong?
HQL queries will not respect a fetch="join" set in mapping. This is because they are freeform queries, making it impossible for NH to guess how to transform them to add the join.
Linq is implemented as a wrapper for HQL, QueryOver is a wrapper for Criteria; that's why you see the different behaviors.
If you need eager loads in Linq/HQL, you will have to make them explicit in the query (using join fetch and Fetch()/FetchMany()

Resources