1

I'm working on a spring boot project, there I have two tables that are related to each other with OneToMany relationship

public class PackGroupEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String description;
    private Double qty;
    private Integer packingNr;
    @Temporal(TemporalType.TIMESTAMP)
    private Date deliveredTime;
    @OneToMany(mappedBy = "packGroup", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    private List<PackArticlesEntity> packArticles= new ArrayList<>();
}

And

public class PackArticlesEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Double qty;
    private Double confirmedName;
    @Enumerated(EnumType.STRING)
    private ArticleStatus status;
    private Double weight;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "pack_group_id")
    private PackGroupEntity packGroup;
}

And I insert data on these two tables in a loop, first I group the articles by packing number for which I will create a PackGroup that has a list of articles:

Map<Integer, List<RecivedArticlesDTO >> groupedArticles =
                    recivedArticlesListDTO.stream().collect(Collectors.groupingBy(RecivedArticlesDTO::getPackingNr));
    for (Map.Entry<Integer, List<RecivedArticlesDTO>> entry : groupedArticles.entrySet()) {

        List<RecivedArticlesDTO > groups = entry.getValue();
        PackGroupEntity packGroup = new PackGroupEntity();
        packGroup.setPackingNr(entry.getKey())
        //some manipulations and setting data for each field 

       List<PackArticlesEntity> listWithArticles = new ArrayList<>();
       groups.forEach(pack -> {
            PackArticlesEntity packArticle= new PackArticlesEntity();
            packArticles.setQty(pack.getQty);
            //some manipulations and setting data for each field 
            listWithArticles.add(packArticles);
      }
      packGroup.setPackArticles(listWithArticles);

      // here I have to save the data using native query 
     packGroupService.savePackGroupData(packGroup);
}

In this way, it is so slow so I wanted to do it on the native query. The problem is that I have many packGroups a many packArticles that needs to be saved. I was thinking to somehow do only one connection with DB to send the list of pack groups and pack articles to save, but I don't know how to do this on native queries. This here is just for one pack_group but yet I don't know how to pass packArticles on a native query since it's a list

@Query(value = "insert  into pack_group " +
        "        (id,packing_nr, description, qty, deliveredTime, packArticles) " +
        "       values (1?, 2?, 3?, 4?, 5?, 6?)", nativeQuery = true)
void savePackGroupData(id, packing_nr, description, qty, packArticles);

Can someone help with this, please?

EDIT: I want to return the id from insert on

    String query = String.format("insert into pack_group(group, remark, description ) " +
 "values ( %s, %s, %s)", "x","y","z"  );
    Query q = entityManager.createNativeQuery(query );
    BigInteger biid = (BigInteger) q.getSingleResult();
    long id = biid.longValue();

And I get this error com.microsoft.sqlserver.jdbc.SQLServerException: The statement did not return a result set.

xarqt
  • 137
  • 1
  • 2
  • 12
  • Can you add tag DBMS you use? – doctorgu Aug 11 '21 at 11:32
  • @doctorgu I added it – xarqt Aug 11 '21 at 12:12
  • If I were you, I will make List into comma separated string. And insert all string at once. You can use string_split if your SQL Server version is 2016 or later, or you can use custom Split function like here. https://stackoverflow.com/questions/5493510/turning-a-comma-separated-string-into-individual-rows/63387847#63387847 I've got 10 to 30 times faster query after changed all list to comma separated string and insert it as one query. – doctorgu Aug 11 '21 at 12:53
  • @doctorgu but on the second insert, I have a list of articles in which I have to use the reference id of the previous inserted pack group. Can you get that on SQL? – xarqt Aug 11 '21 at 13:09
  • I answered. If your case is different with my answer, please let me know it. – doctorgu Aug 11 '21 at 13:44

2 Answers2

0

To speed things up consider using batch updates. It can be a bit tricky with spring data to get it to work, but it do speed things up considerably when working.

See How to do bulk (multi row) inserts with JpaRepository?

If you wish more control over your insert statements then perhaps spring jdbc is a better option :How to do multiple inserts in database using spring JDBC Template batch?

Tomas F
  • 7,226
  • 6
  • 27
  • 36
  • is this a better option than native queries? – xarqt Aug 11 '21 at 10:51
  • Even if you use native queries you would execute n number of inserts if you loop through your list of entities. Keeping down the number of roundtrips to the database is usually recommended if you're trying to improve your performance. Batch inserts is one way. Since you're using JPA then I think it's easier for you do do that with entities and a saveAll on a repository. If you want more control and dynamically create your inserts then perhaps spring jdbc is a better option https://stackoverflow.com/questions/9565481/how-to-do-multiple-inserts-in-database-using-spring-jdbc-template-batch – Tomas F Aug 11 '21 at 11:00
  • I already use Hibernate and jpareository. Can we create a dynamic bulk insert via native query? – xarqt Aug 11 '21 at 12:13
0

This is an answer for 'I have a list of articles in which I have to use the reference id of the previous inserted pack group. Can you get that on SQL?' in comment.

You have Main and Detail table. And you want to insert Main first, and Detail second with Id of Main just inserted.

You can use output inserted of SQL Server to get only currently inserted rows. And you can join it with string_split result to insert into Detail table.

Here's example.

create table Main (
    Id int identity(1, 1),
    Name nvarchar(50)
);
create table Detail (
    MainId int,
    Name nvarchar(50)
);
insert into Main (Name) values ('X'); -- To make new inserted Id starts with 2
declare @MainList nvarchar(1000) = 'A,B,C';
declare @DetailList nvarchar(1000) = 'A2,B2,C2';

declare @IdList table (
    Seq int identity(1, 1),
    Id int
);

-- Insert 3 rows, get all 3 Id using inserted, insert 3 Id to @IdList table
insert into Main (Name)
output inserted.Id into @IdList
select value from string_split(@MainList, ',');

-- Join @IdList table with string_split returned table with Seq
-- Seq of @IdList is auto generated by identity(1, 1)
-- Seq of string_split returned table generated by row_number()
insert into Detail (MainId, Name)
select m.Id MainId, d.value Name
from   @IdList m
       inner join
       (select row_number() over (order by (select 1)) Seq, value
       from    string_split(@DetailList, ',')
       ) d
       on m.Seq = d.Seq;

Result:

select * from Main;
select * from Detail;
Id  Name
--------
1   X
2   A
3   B
4   C

MainId  Name
------------
2   A2
3   B2
4   C2

Demo: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=30213ab0cd59fcb7f541c18c738d4dad

doctorgu
  • 616
  • 6
  • 9
  • Can I execute this with entity manager in spring boot? – xarqt Aug 11 '21 at 14:57
  • is there a way to get the id of inserted record from a native query in hibernate? – xarqt Aug 11 '21 at 18:18
  • @xarqt Yes, you can execute it with EntityManager I found example here https://www.baeldung.com/hibernate-entitymanager#5-querying-for-entities or with with JdbcTemplate https://stackoverflow.com/a/40958508/2958717 – doctorgu Aug 11 '21 at 19:16
  • @xarqt Yes, you can get the id of inserted, just selecting from @IdList like `select Id from @IdList` will return all Id. Because this SQL is not stored procedure, you can run it dynamically. – doctorgu Aug 11 '21 at 19:21
  • can you check please the Edit: I added a new piece of code with the error that I'm getting if you can help me – xarqt Aug 11 '21 at 19:29
  • @xarqt I saw it. You can select currently inserted identity by selecting scope_identity(). Change your code `String query = String.format("insert into pack_group(group, remark, description ) " + "values ( %s, %s, %s)", "x","y","z" );` to `String query = String.format("insert into pack_group(group, remark, description ) " + "values ( %s, %s, %s); select scope_identity() as Id;", "x","y","z" )` – doctorgu Aug 11 '21 at 19:41
  • In the debuger it returns the id but when I run it throws this error `java.lang.ClassCastException: class java.math.BigDecimal cannot be cast to class java.math.BigInteger ` – xarqt Aug 11 '21 at 19:45
  • @xarqt You can change type to integer from SQL like `select cast(scope_identity() as int) as Id` or you can change Java type to BigDecimal like `BigDecimal biid = (BigDecimal) q.getSingleResult();`. – doctorgu Aug 11 '21 at 19:50