I am trying to map native SQL result to my POJO. Here is the configuration. I am using spring.
<bean id="ls360Emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" >
<property name="dataSource" ref="ls360DataSource" />
<property name="jpaVendorAdapter" ref="vendorAdaptor" />
<property name="packagesToScan" value="abc.xyz"/>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
<prop key="hibernate.max_fetch_depth">3</prop>
<prop key="hibernate.jdbc.fetch_size">50</prop>
<prop key="hibernate.jdbc.batch_size">10</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
Here is my Class
#SqlResultSetMapping(
name="courseCompletionMapping",
classes = {
#ConstructorResult(targetClass = CourseCompletion.class,
columns={
#ColumnResult(name = "StoreId", type = String.class),
#ColumnResult(name = "ProductId", type = String.class),
#ColumnResult(name = "UserName", type = String.class),
#ColumnResult(name = "Score", type = Integer.class),
#ColumnResult(name = "CompletionDate", type = Date.class)
}
)
}
)
#Entity
public class CourseCompletion {
private String storeId;
#Id
private String productId;
private String userName;
private int score;
private Date completionDate;
public CourseCompletion() {
}
public CourseCompletion(String storeId, String productId, String userName, int score, Date completionDate) {
this.storeId = storeId;
this.productId = productId;
this.userName = userName;
this.score = score;
this.completionDate = completionDate;
}
// getters and setters
Here how i am calling it
Properties coursePropertiesFile = SpringUtil.loadPropertiesFileFromClassPath("course.properties");
String queryString = coursePropertiesFile.getProperty("course.completion.sql");
long distributorId = 1;
String fromDate = "2009-09-22 00:00:00";
String toDate = "2014-04-11 23:59:59";
Query query = em.createNativeQuery(queryString, "courseCompletionMapping");
//Query query = em.createNamedQuery("findAllEmployeeDetails");
query.setParameter("distributorId", distributorId);
query.setParameter("fromDate", fromDate);
query.setParameter("toDate", toDate);
#SuppressWarnings("unchecked")
List<CourseCompletion> courseCompletionList = query.getResultList();
But when it comes to line
List<CourseCompletion> courseCompletionList = query.getResultList();
I get an error that
Could not locate appropriate constructor on class : mypackage.CourseCompletion
Here is the query that i am trying
select d.DISTRIBUTORCODE AS StoreId, u.USERGUID AS ProductId, u.UserName,
lcs.HIGHESTPOSTTESTSCORE AS Score, lcs.CompletionDate
from VU360User u
inner join learner l on u.ID = l.VU360USER_ID
inner join LEARNERENROLLMENT le on le.LEARNER_ID = l.ID
inner join LEARNERCOURSESTATISTICS lcs on lcs.LEARNERENROLLMENT_ID = le.ID
inner join customer c on c.ID = l.CUSTOMER_ID
inner join DISTRIBUTOR d on d.ID = c.DISTRIBUTOR_ID
where d.ID = :distributorId
and lcs.COMPLETIONDATE is not null
and (lcs.COMPLETIONDATE between :fromDate and :toDate)
and lcs.COMPLETED = 1
Why i am getting this error ?
Thanks
This exception happens because JPA doesn't change column types returned from the database for native queries. Because of this, you have type mismatch. I'm not sure about which column causes this problem in your case (this depends on DBMS you use), but I would suspect you have BigInteger in the result set instead of Integer for score column. To be 100% sure, add a breakpoint to ConstructorResultColumnProcessor.resolveConstructor(Class targetClass, List<Type> types) and investigate. After you find a mismatch, change field type in your mapping class.
Another solution will be not to use #SqlResultSetMapping at all. As your CourseCompletion class is a managed entity, you should be able to map native query to it directly. See this question for more information.
Adding a break point to the ConstructorResultColumnProcessor.resolveConstructor worked for me. My query had a rowNum selection and it's mapped as BigDecimal type. I was having the constructor with a BigInteger.
Thanks. Added debug point to ConstructorResultColumnProcessor.resolveConstructor method. Hibernate result type was BigIntegerType and DTO object return type was Integer. so allMatched flag was false. That's why It throws "Could not locate appropriate constructor on class"
Thanks
Ram
This happened to me because for some reason I specified the constructor as private. The ConstructorResultColumnProcessor.resolveConstructor breakpoint helped me because I realized it never found my constructor.
Related
Thank you for help :)
I tried to get last id, and read many post about it, but i don't arrive to apply it in my case.
First Class
private Date date;
private List<AdsEntity> adsDetails;
... getters and setters
Second Class (AdsEntity)
private int id;
private String description;
There is the code where i try to get the last id :
Mapper
#Insert({
"<script>",
"INSERT INTO tb_ads_details (idMyInfo, adDate)"
+ " VALUES"
+ " <foreach item='adsDetails' index='index' collection='adsDetails' separator=',' statement='SELECT LAST_INSERT_ID()' keyProperty='id' order='AFTER' resultType='java.lang.Integer'>"
+ " (#{adsDetails.description, jdbcType=INTEGER}) "
+ " </foreach> ",
"</script>"})
void saveAdsDetails(#Param("adsDetails") List<AdsDetailsEntity> adsDetails);
In debugging mode, when I watch List I see the id still at 0 and don't get any id.
So what I wrote didn't workout :(
Solution Tried with the answer from #Roman Konoval :
#Roman Konoval
I apply what you said, and the table is fully well set :)
Just one problem still, the ID is not fulfill
#Insert("INSERT INTO tb_ads_details SET `idMyInfo` = #{adsDetail.idMyInfo, jdbcType=INTEGER}, `adDate` = #{adsDetail.adDate, jdbcType=DATE}")
#SelectKey(statement = "SELECT LAST_INSERT_ID()", before = false, keyColumn = "id", keyProperty = "id", resultType = Integer.class )
void saveAdsDetails(#Param("adsDetail") AdsDetailsEntity adsDetail);
default void saveManyAdsDetails(#Param("adsDetails") List<AdsDetailsEntity> adsDetails)
{
for(AdsDetailsEntity adsDetail:adsDetails) {
saveAdsDetails(adsDetail);
}
}
Thank for your help :)
Solution add to #Roman Konoval proposal from #Chris advice
#Chris and #Roman Konoval
#Insert("INSERT INTO tb_ads_details SET `idMyInfo` = #{adsDetail.idMyInfo, jdbcType=INTEGER}, `adDate` = #{adsDetail.adDate, jdbcType=DATE}")
#SelectKey(statement = "SELECT LAST_INSERT_ID()", before = false, keyColumn = "id", keyProperty = "adsDetail.id", resultType = int.class )
void saveAdsDetails(#Param("adsDetail") AdsDetailsEntity adsDetail);
default void saveManyAdsDetails(#Param("adsDetails") List<AdsDetailsEntity> adsDetails)
{
for(AdsDetailsEntity adsDetail:adsDetails) {
saveAdsDetails(adsDetail);
}
}
Thanks to all of you, for the 3 suggestions!!!
yes. it doesnt work.
please take a look at mapper.dtd
foreach-tag doesnt support/provide the following properties statement, keyProperty order and resultType
if you need the id for each inserted item please let your DataAccessObject handle iteration and use something like this in your MapperInterface
#Insert("INSERT INTO tb_ads_details (idMyInfo, adDate) (#{adsDetail.idMyInfo, jdbcType=INTEGER}, #{adsDetail.adDate, jdbcType=DATE})")
#SelectKey(before = false, keyColumn = "ID", keyProperty = "id", resultType = Integer.class, statement = { "SELECT LAST_INSERT_ID()" } )
void saveAdsDetails(#Param("adsDetail") AdsDetailsEntity adsDetail);
please ensure AdsDetailsEntity-Class provides the properties idMyInfoand adDate
Edit 2019-08-21 07:25
some explanation
referring to the mentioned dtd the <selectKey>-tag is only allowed as direct child of <insert> and <update>. it refers to a single Object that is passed into the mapper-method and declared as parameterType.
its only executed once and its order property tells myBatis wether to execute it before or after the insert/update statement.
in your case, the <script> creates one single statement that is send to and handled by the database.
it is allowed to combine #Insert with <script> and <foreach> inside and #SelectKey. but myBatis doesnt intercept/observe/watch database handling the given statement. and as mentioned before, #SelectKey gets executed only once, before or after #Insert-execution. so in your particular case #SelectKey returns the id of the very last inserted element. if your script inserts ten elements, only the new generated id of tenth element will be returned. but #SelectKey requires a class-property with getter and setter to put the selected id into - which List<?> doesnt provide.
example
lets say you want to save an Advertisement and its AdvertisementDetails
Advertisement has an id, a date and details
public class Advertisement {
private List<AdvertisementDetail> adDetails;
private Date date;
private int id;
public Advertisement() {
super();
}
// getters and setters
}
AdvertisementDetail has its own id, a description and an id the Advertisementit belongs to
public class AdvertisementDetail {
private String description;
private int id;
private int idAdvertisement;
public AdvertisementDetail() {
super();
}
// getters and setters
}
the MyBatis-mapper could look like this. #Param is not used, so the properties are accessed direct.
#Mapper
public interface AdvertisementMapper {
#Insert("INSERT INTO tb_ads (date) (#{date, jdbcType=DATE})")
#SelectKey(
before = false,
keyColumn = "ID",
keyProperty = "id",
resultType = Integer.class,
statement = { "SELECT LAST_INSERT_ID()" })
void insertAdvertisement(
Advertisement ad);
#Insert("INSERT INTO tb_ads_details (idAdvertisement, description) (#{idAdvertisement, jdbcType=INTEGER}, #{description, jdbcType=VARCHAR})")
#SelectKey(
before = false,
keyColumn = "ID",
keyProperty = "id",
resultType = Integer.class,
statement = { "SELECT LAST_INSERT_ID()" })
void insertAdvertisementDetail(
AdvertisementDetail adDetail);
}
the DataAccessObject (DAO) could look like this
#Component
public class DAOAdvertisement {
#Autowired
private SqlSessionFactory sqlSessionFactory;
public DAOAdvertisement() {
super();
}
public void save(
final Advertisement advertisement) {
try (SqlSession session = this.sqlSessionFactory.openSession(false)) {
final AdvertisementMapper mapper = session.getMapper(AdvertisementMapper.class);
// insert the advertisement (if you have to)
// its new generated id is received via #SelectKey
mapper.insertAdvertisement(advertisement);
for (final AdvertisementDetail adDetail : advertisement.getAdDetails()) {
// set new generated advertisement-id
adDetail.setIdAdvertisement(advertisement.getId());
// insert adDetail
// its new generated id is received via #SelectKey
mapper.insertAdvertisementDetail(adDetail);
}
session.commit();
} catch (final PersistenceException e) {
e.printStackTrace();
}
}
}
What Chris wrote about inability to get ids in the foreach is correct. However there is a way to implement id fetching in mapper without the need to do it externally. This may be helpful if you use say spring and don't have a separate DAO layer and your mybatis mappers are the Repository.
You can use default interface method (see another tutorial about them) to insert the list of items by invoking a mapper method for single item insert and single item insert method does the id selection itself:
interface ItemMapper {
#Insert({"insert into myitem (item_column1, item_column2, ...)"})
#SelectKey(before = false, keyColumn = "ID",
keyProperty = "id", resultType = Integer.class,
statement = { "SELECT LAST_INSERT_ID()" } )
void saveItem(#Param("item") Item item);
default void saveItems(#Param("items") List<Item> items) {
for(Item item:items) {
saveItem(item);
}
}
MyBatis can assign generated keys to the list parameter if your DB/driver supports multiple generated keys via java.sql.Statement#getGeneratedKeys() (MS SQL Server, for example, does not support it, ATM).
The following example is tested with MySQL 5.7.27 + Connector/J 8.0.17 (you should include version info in the question).
Be sure to use the latest version of MyBatis (=3.5.2) as there have been several spec changes and bug fixes recently.
Table definition:
CREATE TABLE le tb_ads_details (
id INT PRIMARY KEY AUTO_INCREMENT,
description VARCHAR(32)
)
POJO:
private class AdsDetailsEntity {
private int id;
private String description;
// getters/setters
}
Mapper method:
#Insert({
"<script>",
"INSERT INTO tb_ads_details (description) VALUES",
"<foreach item='detail' collection='adsDetails' separator=','>",
" (#{detail.description})",
"</foreach>",
"</script>"
})
#Options(useGeneratedKeys = true, keyProperty="adsDetails.id", keyColumn="id")
void saveAdsDetails(#Param("adsDetails") List<AdsDetailsEntity> adsDetails);
Note: You should use batch insert (with ExecutorType.BATCH) instead of multi-row insert (=<foreach/>) when inserting a lot of rows.
I have a route class, in this class, I defined a collection of locations.
#ManyToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
#JoinTable(name = "route_location_map")
private List<Location> locations;
What I required is to check a particular location is present in my route
if (locationId != null && locationId.longValue() > 0) {
Expression<Collection<Long>> locations = route.get("locations").get("id");
predicate = builder.isMember(locationId, locations);
whereClauseList.add(predicate);
Unfortunately, this logic is not working, I didn't get any error too.
Can anybody help me to resolve this?
Post next your stacktrace. You need to use isMember() with your Entity or Expression.
public List<Route> findRoutes(Location location){
...
Expression<Collection<Location>> locations = route.get("locations");
predicate = builder.isMember(location, locations);
...
}
I use
criteriabuilder.ge() to get values greater to a given time period but getting compilation issue states following error;
The method ge(Expression<? extends Number>, Expression<? extends Number>) in the type CriteriaBuilder is not applicable for the arguments (Path, ParameterExpression<capture#6-of ? extends LocalDateTime>)
Code
public Crud<T> greaterDate(String property, String value) {
CriteriaBuilder cb = getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(getEntityClass());
Root<T> root = cq.from(getEntityClass());
ParameterExpression<? extends LocalDateTime> expression = cb.parameter(LocalDateTime.class);
cq.select(root);
Predicate predicate = cb.ge(root.get(getEntityType().getSingularAttribute(property)), expression);
How can I use ge/gt/lt for LocalDateTime objects?
I have a pojo that contains a property name, logic operator as String and the value of property. What I want to accomplish is create a Predicate or Expression etc dynamically from the pojo data. Below are my code:
public class QueryParam {
private String property = "acctType"; //can be any property of classname
private String operator = "eqic" //can be any logic operator !=, >, <, >=, <= etc
private Object value; //will store the value of
// getters/setters here
}
public interface CustomerRepository extends JpaRepository<Customer, Long>, QueryDslPredicateExecutor<Customer>{
}
#Service("CustomerService")
class MyCustomerServiceImpl {
#Resource
private CustomerRepository custRpstry;
//if classname is Customer, property is "acctType", operator is "eqic", and value is "Corporate"
//I want my findAll below to retrieve all Customers having acctType = "Corporate"
List<Customer> findAll(List<QueryParam> qryParam) {
QCustomer qcust = QCustomer.customer;
BooleanBuilder where = new BooleanBuilder();
for(QueryParam param : qryParam) {
//within this block, i want a BooleanBuilder to resolve to:
where.and(qcust.acctType.equalsIgnoreCase("Corporate"));
something like:
where.and(param.getClassname().param.getProperty().param.getOperator().param.getValue())
}
return custRpstry.findAll(where.getValue()).getContent();
}
}
I can't figure out to formulate my BooleanBuilder especially the portion that will convert
getOperator() into .equalIgnoreCase().
Any help would be greatly appreciated.
Thanks in advance,
Mario
After combining several answers to some related questions here in so, I was able to formulate a solution that works for me.
BooleanBuilder where = new BooleanBuilder();
for(QueryParam param: qryParam) {
//create: Expressions.predicate(Operator<Boolean> opr, StringPath sp, filter value)
//create an Operator<Boolean>
Operator<Boolean> opr = OperationUtils.getOperator(param.getOperator().getValue());
//create a StringPath to a class' property
Path<User> entityPath = Expressions.path(Customer.class, "customer");
Path<String> propPath = Expressions.path(String.class, entityPath, param.getProperty());
//create Predicate expression
Predicate predicate = Expressions.predicate(opr, propPath, Expressions.constant(param.getValue()));
where.and(predicate);
}
list = repository.findAll(where.getValue(), pageReq).getContent();
My OperationUtils.java
public class OperationUtils {
public static com.mysema.query.types.Operator<Boolean> getOperator(String key) {
Map<String, com.mysema.query.types.Operator<Boolean>> operators = ImmutableMap.<String, com.mysema.query.types.Operator<Boolean>>builder()
.put(Operator.EQ.getValue() ,Ops.EQ)
.put(Operator.NE.getValue() ,Ops.NE)
.put(Operator.GT.getValue() ,Ops.GT)
.put(Operator.GTE.getValue() ,Ops.GOE)
.put(Operator.LT.getValue() ,Ops.LT)
.put(Operator.LTE.getValue() ,Ops.LOE)
.build();
return operators.get(key);
}
}
I have following query:
SELECT DISTINCT *
FROM Projekt p
WHERE p.bewilligungsdatum = to_date('01-07-2000', 'dd-mm-yyyy')
but i have problems to build the conditions. Here my code:
condition = criteriaBuilder.equal((Expression<String>) projekt.get(criterion), "to_date('" + projektSearchField + "', 'dd-mm-yyyy')");
this generate following:
SELECT DISTINCT *
FROM Projekt p
WHERE p.bewilligungsdatum = 'to_date('01-07-2000', 'dd-mm-yyyy')'
and ufcorse doesn't work. Which method should i use for date comparision (or how to remove the outer ' chars in the pattern part)?
why don't you try to work with parameters like that. Then you can do the String->Date conversion in java and pass a real java.util.Date to the database.
EntityManager em; // initialized somewhere
Date datum; // initialized somewhere
...
String queryString = "SELECT p "
+ "FROM Projekt p"
+ "WHERE p.bewilligungsdatum = :datum";
Query query = em.createQuery(queryString)
query.setParameter("datum", datum);
List<Projekt> projekte = query.getResultList();
This is the way to stay DB independent because your are not using the specific to_date function
viele Grüße aus Bremen ;o)
This should work too, by passing a date as parameter of a restriction
Date datum; // initialized somewhere
CriteriaQuery query = ...
query.add(Restrictions.eq( "bewilligungsdatum ", datum );
...
Sorry. I had the hibernate CriteriaQuery in mind.
Then try via the CriteriaBuilder somthing like that
Date datum; // initialized somewhere
...
final CriteriaQuery<Projekt> query = criteriaBuilder.createQuery(Projekt.class);
final Root<Projekt> projekt = query.from(Projekt.class);
Predicate condition = criteriaBuilder.equals(projekt.get("bewilligungsdatum"),datum);
query.where(condition)
I did not use this before, so have a try on your own
you can use https://openhms.sourceforge.io/sqlbuilder/ ,then use the Condition like
Object value1 = hire_date
Object value2 = new CustomObj("to_date('2018-12-01 00:00:00','yyyy-MM-dd HH:mm:ss')")
//CustomObj
public class CustomObj extends Expression {
private Object _value;
public CustomObj(Object value) {
_value = value;
}
#Override
public boolean hasParens() {
return false;
}
#Override
protected void collectSchemaObjects(ValidationContext vContext) {
}
#Override
public void appendTo(AppendableExt app) throws IOException {
app.append(_value);
}
}
BinaryCondition.greaterThan(value1, value2, inclusive);
the sql like hire_date >= to_date('2011-02-28 00:00:00','yyyy-MM-dd HH:mm:ss'))