3

I have Spring Batch with a basic chunk:

  1. Read a CSV file.
  2. Process it (Transcode some coded value into another ones).
  3. Write the result into a database.

The problem

In some case, I want insert or update mechnism in my custom ItemWriter.

Exemple: I can get this 2 lines on my CSV

C006;Test;OK;01/01/1970;1
C006;Test;OK;01/01/1970;5

You can figure out that they are pretty similar except the last column, the strategy will be:

  1. Check the database if I have a "like so" entity
  2. if true, update the value with the last item that you get (in our case, the second line with the value 5 in the last column)

What I already did?

In my entity bean, I added a Unique Constraint anotation like so:

@Table(name = "indicator",
    uniqueConstraints = { @UniqueConstraint(columnNames = 
                                        { "reference", "type", "status", "date" }) })

I'm sure now that I cannot persist an entity with the same column's data. Then, I created a custom ItemWriter, and I tried to catch the ConstraintViolationException but it didn't work, I always get an other exeption, even when I tried the parent one.

So do you have any ideas or another way to do it?

I was think about using merge JPA functionality? what do you think about it?

My custom ItemWriter

@Component
public class ImportItemWriter implements ItemWriter<Indicator>{

    @Autowired
    protected IndicatorDao indicatorDao;

    public void write(List<? extends Indicator> items) throws Exception {

        for (Indicator item : items) {
            Indicator indicator = new Indicator();

            indicator.setReference(item.getReference());
            indicator.setType(item.getType());
            indicator.setStatus(item.getStatus());
            indicator.setDueDate(item.getDueDate());

            indicator.setValue(item.getValue());

            try {
                indicatorDao.persist(indicator);
            } catch (ConstraintViolationException e) {
                // TODO: handle exception
            }       
        }
    }
}

Update Problem Solved The idea of using Composite PKey is interesting, but I can't use it because I have to create a composite with 9 keys that's not fair in term of performance. I decided to add function to my DAO (isDuplicated) and in my custom ItemWriter, I just make a simple test:

if `isDuplicated()` then `updateEntity()` else `insertNew()`
noob
  • 774
  • 1
  • 10
  • 23
TheCyberXP
  • 915
  • 1
  • 12
  • 24
  • 1
    Have you thought about using [this](http://docs.spring.io/spring-batch/trunk/apidocs/org/springframework/batch/item/database/JpaItemWriter.html) directly? .. the implementation performs a entityManger.merge(..) .. if your entity have a composite pkey you could solve in this way. – Xstian Sep 18 '14 at 15:27
  • Yep, I used JpaItemWriter before, but didn't work, I guess that the merge catch only with the primary key and in my case I don't care about it, I have a specific purpose, checking simultaneously 4 attributes – TheCyberXP Sep 18 '14 at 15:44
  • 1
    You could create a composite primary key with your 4 attributes :) .. but if you can't change your entity, is the same thing your "ImportItemWriter". ;) see [this link](http://uaihebert.com/tutorial-jpa-composite-primary-key/) to create composite primary key – Xstian Sep 18 '14 at 15:46
  • 2
    Interesting, I'll give a try. Thanks – TheCyberXP Sep 18 '14 at 15:50
  • have you solved this issue? if yes, how did you solve it? – Xstian Oct 13 '14 at 13:49

2 Answers2

8

One option would be to use the ClassifierCompositeItemWriter. The Classifier would be used to determine whether to do the insert or the update. Then you'd configure two delegates, one for inserts, one for updates.

Michael Minella
  • 20,843
  • 4
  • 55
  • 67
  • That didn't solde the problem of catching whether i have to insert or update – TheCyberXP Sep 18 '14 at 16:12
  • What part of your needs aren't addressed with this solution? – Michael Minella Sep 18 '14 at 17:14
  • The idea of using a Classifier is not bad at all, but in my case I have another problem. Basically, I would like to find a mechanism that allow me to identify whether I need to update or insert an item. Then, inserting or updating is not a problem, but first I need to catch the case. Thanks ;) – TheCyberXP Sep 19 '14 at 07:11
2

Summarize the above comments.

Have you thought about using this directly? .. the implementation performs a entityManger.merge(..) .. if your entity have a composite pkey you could solve in this way.

    <job id="writeProductsJob" xmlns="http://www.springframework.org/schema/batch">
        <step id="readWrite">
            <tasklet>
                <chunk reader="productItemReader" writer="productItemWriter" commit-interval="3" />
            </tasklet>
        </step>
    </job>

    <bean id="productItemWriter" class="org.springframework.batch.item.database.JpaItemWriter">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

By an @EmbeddedId you can build a composite primary key in order to merge the entity. See this link

Entity

@Entity
@Table(name = "CAR")
public class Car {

    @EmbeddedId
    private CarPK carPK;

    @Column
    private String name;
}

Composite Primary Key

@Embeddable
public class CarPK implements Serializable {

    @Column
    private String chassisSerialNumber;

    @Column
    private String engineSerialNumber;

    public CarPK(){
        // Your class must have a no-arq constructor
    }

    @Override
    public boolean equals(Object obj) {
        if(obj instanceof CarPK){
            CarPK carPk = (CarPK) obj;

            if(!carPk.getChassisSerialNumber().equals(chassisSerialNumber)){
                return false;
            }

            if(!carPk.getEngineSerialNumber().equals(engineSerialNumber)){
                return false;
            }

            return true;
        }

        return false;
    }

    @Override
    public int hashCode() {
        return chassisSerialNumber.hashCode() + engineSerialNumber.hashCode();
    }

    //setter and getter
}
Xstian
  • 8,184
  • 10
  • 42
  • 72
  • 1
    Great idea, I didn't knew that there was something like composite Pkey before! but before testing, conceptually speaking, is that good to create an entity that contains a composite pkey with 9 arguments? – TheCyberXP Sep 19 '14 at 08:30
  • I think that conceptually is correct if the set of each fields must be unique in your db. Is a god way also because that is handled by ORM. JPA does not have significant performance impact whether its a composite key or not. [see this link](http://stackoverflow.com/a/7937210/3364187) – Xstian Sep 19 '14 at 08:55