MyBatis inserts one to many relationship - insert

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.

Related

Get Records on the basis of list of string Criteria Query Predicates

I created one class
class Employee { Integer id; String name; String departments; }
and in sql server database i have records
I stored departments as ";" separated. For Example Department = Computer;Civil
1,Chaitanya,Computer;Civil
2,Tom,Physics;Chemistry
3,Harry,Economics;Commerce
4,Henry,Computer;Civil;Mechanical
5,Ravi,null
Now i want to filter data with departments let's say there is one multiselect in frontend where i have list of departments and i select two departments for example-> Computer,Civil and in backend i got List<String> deparmentFilter as parameter say Computer;Civil
Now as per my requirement i have to return two data from Spring Boot Controller
1,Chaitanya,Computer;Civil
4,Henry,Computer;Civil;Mechanical
Right Now what i did is i executed the query to fetch all the records and then i right below logic
List<Employee> employeesToBeRemoved = new ArrayList<>();
if (!departmentNames.isEmpty()) {
allEmployees.forEach(employee -> {
if (employee.getDepartment() != null) {
Set<String> departmentNamesResult = new HashSet<>(Arrays.asList(employee.getDepartment().
split(";")));
Boolean isExist = Collections.disjoint(departmentNamesResult, departmentNames);
if (Boolean.TRUE.equals(isExist)) {
employeesToBeRemoved.add(employee);
}
} else {
employeesToBeRemoved.add(employee);
}
});
}
allEmployees.removeAll(employeesToBeRemoved);
I tried to move it to predicates but not able to do that, This solution is taking much time to execute,
Please suggest me some other better ways (optimized way) to improve performance.
Is there is any way to add this filter in predicates?
Another approach i am thinking (12/05/2022)
Let's say i have one table employee_department_mapping and in that table i have employeeId and departmentName so in this correct way to add predicate?
CriteriaQuery<Object> subQuery1 = criteriaBuilder.createQuery();
Root<EmployeeDepartmentMapping> subQueryEmpDptMp = subQuery1.from(EmployeeDepartmentMapping.class);
predicates1.add(subQueryEmpDptMp.get("departmentName").in(departmentNames));
You might achieve better performance by splitting your table and using join:
class Employee { Integer id; String name; Integer departmentsId; }
class EmployeeDepartments { Integer departmentsId; String department; }
You may use Element Collection to achieve this.
Now, instead of having a the following row:
1,Chaitanya,Computer;Civil
You will have the following:
table1:
1,Chaitanya,123
table2:
123,Compter
123,Civil
Execute a join to get all row from table2 with table1 to get your result

Multiple selection from different tables in one query

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.

MyBatis foreach: there is no getter for property error

I'm getting 'there is no getter for property' error while executing foreach loop in mybatis. From the form I am gettin an array of strings (hashtag). In my PostInfoVO, I have a list of string like this:
private List<String> hashtags;
and its getters and setters like this:
public List<String> getHashtags() {
return hashtags;
}
public void setHashtags(List<String> hashtags) {
if (this.items == null)
this.items = new ArrayList<String>();
}
I checked that the list of hashtags is passing data to cotroller by logging postvo.getHashtags().toString().
In my MyBatis file, I have the following foreach loop which is returning 'there is no getter for property hashtag in PostInfoVO.
<![CDATA[
BEGIN
<foreach collection="hashtags" item="hashtag" separator=",">
INSERT INTO TBL_HASHTAG_INFO(post_id, hashtag)
VALUES
((SELECT post_id FROM TBL_POST_INFO ORDER BY post_id DESC LIMIT 1), #{hashtag})
</foreach>;
]]>
END;
List of things I tried:
Removing Begin and end
Taking Insert statement out of foreach loop
Open="(", Close=")"
Any advice would be appreciated! Thank you so much.
There are two problems in your code : your field name is hashtags but you used this.items in setter method and with this setter method you implemented the argument of setter method never assigns to the hashtags(field) ,Edit it as below :
public void setHashtags(List<String> hashtags) {
this.hashtags = hashtags;
}

Issue with writing named sql query with hibernate

I am trying to access database FK using named SQL query with Hibernate, the idea is to query a customer table which contains name, and companyId,etc. CompanyId is the FK for a commpany table. The query I wrote is as follows:
#NamedNativeQuery(name="getcustomer", query="Select CUSTOMER.* from CUSTOMER,COMPANY where CUSTOMER_FIRST_NAME = (?1) and CUSTOMER_LAST_NAME= (?2) and CUSTOMER_COMPANY_ID_FK = (?3) ",resultClass=Customer.class)
The issue I am currently having as follow:
Exception in thread "main" org.hibernate.QueryParameterException:
Position beyond number of declared ordinal parameters. Remember that
ordinal parameters are 1-based! Position: 2 at
org.hibernate.engine.query.spi.ParameterMetadata.getOrdinalParameterDescriptor(ParameterMetadata.java:89)
at
org.hibernate.engine.query.spi.ParameterMetadata.getOrdinalParameterExpectedType(ParameterMetadata.java:109)
at
org.hibernate.internal.AbstractQueryImpl.determineType(AbstractQueryImpl.java:507)
at
org.hibernate.internal.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:479)
at
com.comresource.scrapmetalapp.DAOImpl.CustomerDAOImpl.searchCustomer(CustomerDAOImpl.java:61)
at
com.comresource.scrapmetalapp.ServiceImpl.CustomerServiceImpl.searchCustomer(CustomerServiceImpl.java:39)
at com.comresource.scrapmetalapp.Config.Run.main(Run.java:57)
My DAO implementation is like this:
#Override
public Customer searchCustomer(String fName, String lName, Integer company) {
Session session = sessionFactory.openSession();
return (Customer) session.getNamedQuery("getcustomer").setParameter(1, fName)
.setParameter(2, lName)
.setParameter(3, company)
.uniqueResult();
}
What is the issue here?
For this, I would need to see how you are associating the mapping in your model class, but the query should go like this.
public Customer getMeThatCustomer(String param1, String param2, int foreignkey){
session = getCurrentSession();
org.hibernate.Query query = session.createQuery("From Customer as c where c.name=:param1 and c.lastname=:param2 and c.company.companyid=:foreignkey");
//Note the last parameter, where I have mentioned c.company, in place of
company, there should be the foregin key association and then the primary key in java class.
query.setParameter("param1",param1);
query.setP...er("param2",param2);
quer.....("companyid",companyid);
return (Customer) query.uniqueResult();
}
So, try it out, let me know if there is any problem

Using distinct in Spring data over multiple columns

My domain model is like this:
CollectedData {
String name;
String description;
int count;
int xAxis,
int yAxis
}
Using Spring data repository query, I would like to retrieve all the unique rows (unique with name, xAxis, yAxis)
I am trying something like this
#Query("select distinct a.name, a.xAxis, a.yAxis from CollectedData a")
List<CollectedData> findAllDistinctData();
So, when I do
List<CollectedData> records= findAllDistinctData();
for (CollectedData record : records) { //Exception on this line
}
Exception
[Ljava.lang.Object; cannot be cast to CollectedData.
Is there any other way to write query for this ?
#Query return ArrayList of Object(s) instead of specific type of object. so you have to define some thing like
#Query("select distinct a.name, a.xAxis, a.yAxis from CollectedData a")
List<Object> findAllDistinctData();
then cast according to your requirement,
List<Object> cdataList=findAllDistinctData();
for (Object cdata:cdataList) {
Object[] obj= (Object[]) cdata;
String name = (String)obj[0];
String description = (String)obj[1];;
...
}
Instead of returning an object you can use JPA's constructor expression feature to return a more specific object holding only the columns you're interested in. See also following answer:
JPQL Constructor Expression - org.hibernate.hql.ast.QuerySyntaxException:Table is not mapped
According to your example you could create a new Object with only the columns you are interested in:
SELECT DISTINCT new com.mypackage.MyInterestingCollectedData(a.name, a.xAxis, a.yAxis) from CollectedData a
If you want to select complete object based on distinct values of multiple columns,
In that case the native query would be the option.
e.g.
#Query(
value = "select distinct on (column1, column2, column3) * From my_table where someId=: order by column1 asc,column2 desc,column3 desc,column4 desc",
nativeQuery = true
)
fun finalAllDistinctBy(containerId: String): List<MyTable>

Resources