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 am a dummy in using spring aggregations.
I do have this entity document:
#Document(collection = "DocumentFile")
public class DocumentFile {
private String projectId;
private String originalFileName;
and I will get the amount of documentFiles which have the same projectId grouped by originalFileName (so DocumentFile's with same name should only be counted once)
This is my approach but I don't know how to get now the result/amount.
final Aggregation agg = newAggregation(match(Criteria.where("projectId").in(projectId)),
group("originalFileName").count().as("amountOfDocumentFiles"));
Assuming that aggregate present in the post is correct. Here is the sample code to execute the aggregate using MongoOperations and get the result.
In my project, I get the MongoOperations object like this.
public MongoOperations getMongoConnection() {
return (MongoOperations) new AnnotationConfigApplicationContext(SpringMongoConfig.class)
.getBean("mongoTemplate");
}
Execute aggregate and get results:-
Aggregation aggregate = newAggregation(match(Criteria.where("projectId").in(projectId)),
group("originalFileName").count().as("amountOfDocumentFiles"));
AggregationResults<DocumentFile> documentFileAggregate = mongoOperations.aggregate(aggregate,
"DocumentFile", DocumentFile.class);
if (documentFileAggregate != null) {
System.out.println("Output ====>" + documentFileAggregate.getRawResults().get("result"));
System.out.println("Output ====>" + documentFileAggregate.getRawResults().toMap());
}
I have a JPA entity User which contains a field (entity) City. I want to select one page of, for example, 10 users but from different cities.
In SQL I would use something like:
SELECT DISTINCT ON (u.city_id) u.username ,u.email, u.city_id ....
FROM user u LIMIT 0,10 ....
but I need to do it with JPQL or JPA criteria builder. How can I achieve this?
Recently I came across same situation, found that there is no direct way using criteria query to support it.
Here is my solution -
Create custom sql function for distinct on
register function to dialect
Update dialect in properties
call it from criteria query
1) Create Custom function
public class DistinctOn implements SQLFunction {
#Override
public boolean hasArguments() {
return true;
}
#Override
public boolean hasParenthesesIfNoArguments() {
return true;
}
#Override
public Type getReturnType(Type type, Mapping mapping) throws QueryException {
return StandardBasicTypes.STRING;
}
#Override
public String render(Type type, List arguments, SessionFactoryImplementor sessionFactoryImplementor) throws QueryException {
if (arguments.size() == 0) {
throw new QueryException("distinct on should have at least one argument");
}
String commaSeparatedArgs = String.join(",",arguments);
return " DISTINCT ON( " + commaSeparatedArgs + ") " + arguments.get(0) + " ";
}
}
2) Register Function
public class CustomPostgresSqlDialect extends PostgreSQLDialect {
public CustomPostgresSqlDialect() {
super();
registerFunction("DISTINCT_ON", new DistinctOn());
}
}
3) Update Dialect :
Here pass on your class name
spring.jpa.properties.hibernate.dialect = com.harshal.common.CustomPostgresSqlDialect
4) Use it in Criteria Query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
// SELECT DISTINCT ON (u.city_id) u.username
query.multiselect(
cb.function("DISTINCT_ON", String.class, user.get("city")),
user.get("userName")
);
return em.createQuery(query).getResultList();
You can do this by using Hibernate Criteria Query
sample code can be like this
Criteria criteria = session.createCriteria(user.class);
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.distinct(projectionList.add(Projections.property("city_id"), "cityId")));
projectionList.add(Projections.property("username"), "username");
projectionList.add(Projections.property("email"), "email");
criteria.setProjection(projectionList2);
criteria.setResultTransformer(Transformers.aliasToBean(user.class));
List list = criteria.list();
I need to execute a Raw SQL query that returns multiple result sets. Is that possible in EF CF ?
Thanks!
Description
Yes you can! ;) You can perform a raw sql query using DbCommand in Entity Framework Code First too.
You can perform queries with multiple result sets and jump to the next result set using NextResult() method of the SqlDataReader class.
Sample
namespace MyNamespace
{
public class MyDbContext : DbContext
{
}
public class Test
{
public void Test()
{
MyDbContext context = new MyDbContext();
DbCommand db = context.Database.Connection.CreateCommand();
db.CommandText = "SELECT propertie1 FROM Table1; SELECT propertie1 from Table2";
try
{
DbDataReader reader = db.ExecuteReader();
while (reader.Read())
{
// read your data from result set 1
string value = Convert.ToString(reader["propertie1"]);
}
reader.NextResult();
while (reader.Read())
{
// read your data from result set 2
string value = Convert.ToString(reader["propertie1"]);
}
}
catch
{
// Exception handling
}
finally
{
if (db.Connection.State != ConnectionState.Closed)
db.Connection.Close();
}
}
}
}
More Information
Using DbContext in EF 4.1 Part 10: Raw SQL Queries
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'))