2

I am trying to insert a list of rows(questions) to a table.(lets say 'Question_Table'). The whole process is performed in a single transaction. (ie. either i have to insert all questions or none). I am using Spring's declarative transaction.

I have customized the ID generation for Question_Table.(Ref : Custom id generation)

It works for the first question. But it wont work for the second question as the first row is un-committed and the table will be empty. I am not able to inject the DAO class into Id generator as it is not a spring managed bean(so i can have a method in DAO class that reads un-committed records).

What is the best approach to use in this situation.

Generator class

public class IdGenerator implements IdentifierGenerator, Configurable {


    private String prefix = "";
    private String queryKey = "";

    @Override
    public Serializable generate(SessionImplementor sessionImpl, Object arg1) throws HibernateException {

        long count = (long)sessionImpl.getNamedQuery(queryKey).list().get(0);
        System.out.println("COUNT >>> "+count);

        long id = count + 1;
        if(id == 4) throw new NullPointerException();
        String generatedId = prefix + id;

        return generatedId;
    }

    @Override
    public void configure(Type arg0, Properties arg1, ServiceRegistry arg2) throws MappingException {
        prefix=arg1.getProperty("PREFIX");
        queryKey=arg1.getProperty("QUERY_KEY");
    }

}

Query : select count(*) from Question_Table

Renjith
  • 1,122
  • 1
  • 19
  • 45
  • Spring transaction gets committed just after last line of code in your method on which you have annotated @Transactional.. So i would suggest to use flush() command after each insert method call. – Yogi Oct 09 '17 at 08:29
  • It conflicts with the sentence (The whole process is performed in a single transaction(ie. either i have to insert all questions or none)) – Renjith Oct 09 '17 at 08:46
  • flush() is not commit(), so when you flush you still cannot get that data in another session. And once the transaction become broken, it will rolled back automatically. So this not solve your problem nor it break the atomicity. Please tell us more about what is the patern of the ID that you wanted. – Angga Oct 09 '17 at 08:56
  • pattern is QSTN_1, QSTN_2 etc.. so i basically check the row count and add one to it. (ie. "QSTN_"+(row_count + 1)). I tried the flush approach with 5 rows to insert and intentionally throwing an exception when count +1 =4. it inserted 3 rows to db – Renjith Oct 09 '17 at 09:02
  • updated question with generator class – Renjith Oct 09 '17 at 09:05
  • I think [this is similar with what you want](https://stackoverflow.com/questions/37747218/how-to-implement-a-custom-string-sequence-identifier-generator-with-hibernate) the key is using nextVal instead of using count – Angga Oct 09 '17 at 09:18

3 Answers3

1

As i stated in the comment, you maybe can use this approach if you did not have problem using combination of string and sequence. But the downside is the value will always increase even after you delete all record in that table.

If you insist of using count, then the solution is to define your entity id on save manually like. .save(question, "QSTN_"+(row_count + i)); but you will need to be able pass that row_count which i think is not a problem since it must be on one request.

Angga
  • 2,305
  • 1
  • 17
  • 21
  • I took '"QSTN_"+(row_count + i)' as a temporary solution. But I have to look into @Gab's approach. – Renjith Oct 09 '17 at 10:54
0

I have no answer to your specific question but i'd like to share some considerations.

If your id generation depends on the database state, then it must be done at the database level (implementation is up to you, autoincrement, custom function or sequences, etc, etc)...

Otherwise if you do it at the application level you will necessary encounter concurrent access problems and have to mitigate it using some lock or dedicated transaction which will have a significant impact on the application performance and may become inconsistent later (when adding horizontal scalability or sharding for example).

However if you want to generate your ids in an applicative layer (which can be a very good idea) then you must have an unique, distributed system dedicated for this task which is not part of your current unit of work.

Gab
  • 7,869
  • 4
  • 37
  • 68
0
@Transactional(isolation = Isolation.READ_COMMITTED)
public AccountDto saveAccount(AccountDto accountDto) {
    Long accountTypeId = accountDto.getAccountTypeId();
    AccountTypes accountTypes = accountTypesDao.getById( accountTypeId ).orElseThrow( NotFoundAppException::new );
    account.setAccountName( newAccountName );
    account.setAccountType( accountTypes );
    ...
    accountDao.save( account );
    accountDao.flush();

    // new inserted account id is in the transaction now
    return createAccountDtoFrom( account );
}
dobrivoje
  • 848
  • 1
  • 9
  • 18