PAA
PAA

Reputation: 12005

Spring Data JPA - While saving duplicate Composite Key doesn't get any error

I am developing Spring Boot JPA Composite key example using Postgres. In this example, when I'm trying to save record, why I dont see any exceptions or constraint violation exception?

SongId.java

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Embeddable
public class SongId implements Serializable {
    private static final long serialVersionUID = 1L;

    private String name;
    private String album;
    private String artist;
}

Song.java

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Song {
    @EmbeddedId 
    private SongId id;

    private int duration;
    private String genre;
    private LocalDateTime releaseDate;
    private int rating;
    private String downloadUrl;
}

SongsRepository.java

public interface SongsRepository extends JpaRepository<Song, Long>{

}

MainApp.java

@SpringBootApplication
public class CompositeApplication implements CommandLineRunner{

    public static void main(String[] args) {
        SpringApplication.run(CompositeApplication.class, args);
    }
    @Autowired
    private SongsRepository repo;


    @Override
    public void run(String... args) throws Exception {
        SongId songId1 = SongId.builder().name("John").album("AlbumA").artist("ArtistA").build();

        Song song = Song.builder().id(songId1).downloadUrl("http://www.gmail.com").duration(23)
                .genre("MyGene").rating(1).releaseDate(LocalDateTime.now()).build();
        Song song2 = Song.builder().id(songId1).downloadUrl("http://www.gmail.com").duration(23)
                .genre("MyGene").rating(1).releaseDate(LocalDateTime.now()).build();

        try {
            repo.saveAll(Arrays.asList(song, song2));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

Edit-1: I changed SongsRepository.java to

public interface SongsRepository extends CrudRepository<Song, SongId>{}

and main Method code.

@Override
    public void run(String... args) throws Exception {
        SongId songId1 = SongId.builder().name("John").album("AlbumA").artist("ArtistA").build();

        Song song = Song.builder().songId(songId1).downloadUrl("http://www.gmail.com").duration(23)
                .genre("MyGene").rating(4).releaseDate(LocalDateTime.now()).build();
        Song song2 = Song.builder().songId(songId1).downloadUrl("http://www.yahoo.com").duration(25)
                .genre("Sample Testung").rating(2).releaseDate(LocalDateTime.now()).build();

        repo.saveAll(Arrays.asList(song, song2));

    }

Logs:

2019-07-03 19:56:31.901  INFO 5420 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 46ms. Found 1 repository interfaces.
2019-07-03 19:56:32.265  INFO 5420 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2019-07-03 19:56:32.378  INFO 5420 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2019-07-03 19:56:32.415  INFO 5420 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
2019-07-03 19:56:32.466  INFO 5420 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {5.3.10.Final}
2019-07-03 19:56:32.467  INFO 5420 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2019-07-03 19:56:32.600  INFO 5420 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.4.Final}
2019-07-03 19:56:32.747  INFO 5420 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQLDialect
2019-07-03 19:56:32.895  INFO 5420 --- [           main] o.h.e.j.e.i.LobCreatorBuilderImpl        : HHH000421: Disabling contextual LOB creation as hibernate.jdbc.lob.non_contextual_creation is true
2019-07-03 19:56:32.899  INFO 5420 --- [           main] org.hibernate.type.BasicTypeRegistry     : HHH000270: Type registration [java.util.UUID] overrides previous : org.hibernate.type.UUIDBinaryType@4ad3d266
Hibernate: 

    drop table if exists composite.song cascade
Hibernate: 

    create table composite.song (
       album varchar(255) not null,
        artist varchar(255) not null,
        name varchar(255) not null,
        download_url varchar(255),
        duration int4 not null,
        genre varchar(255),
        rating int4 not null,
        release_date timestamp,
        primary key (album, artist, name)
    )
2019-07-03 19:56:33.350  INFO 5420 --- [           main] o.h.t.schema.internal.SchemaCreatorImpl  : HHH000476: Executing import script 'org.hibernate.tool.schema.internal.exec.ScriptSourceInputNonExistentImpl@41ad373'
2019-07-03 19:56:33.352  INFO 5420 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2019-07-03 19:56:33.404 DEBUG 5420 --- [           main] .c.JpaMetamodelMappingContextFactoryBean : Initializing JpaMetamodelMappingContext…
2019-07-03 19:56:33.410 DEBUG 5420 --- [           main] .c.JpaMetamodelMappingContextFactoryBean : Finished initializing JpaMetamodelMappingContext!
2019-07-03 19:56:33.557 DEBUG 5420 --- [           main] o.s.d.r.c.s.RepositoryFactorySupport     : Initializing repository instance for com.example.repository.SongsRepository…
2019-07-03 19:56:33.603 DEBUG 5420 --- [           main] o.s.d.j.r.query.JpaQueryFactory          : Looking up query for method findBySongId_AlbumAndSongId_ArtistAndSongId_Name
2019-07-03 19:56:33.604 DEBUG 5420 --- [           main] o.s.d.jpa.repository.query.NamedQuery    : Looking up named query Song.findBySongId_AlbumAndSongId_ArtistAndSongId_Name
2019-07-03 19:56:33.606 DEBUG 5420 --- [           main] o.s.d.jpa.repository.query.NamedQuery    : Did not find named query Song.findBySongId_AlbumAndSongId_ArtistAndSongId_Name
2019-07-03 19:56:33.646 DEBUG 5420 --- [           main] o.s.d.r.c.s.RepositoryFactorySupport     : Finished creation of repository instance for com.example.repository.SongsRepository.
2019-07-03 19:56:33.713  INFO 5420 --- [           main] com.example.CompositeApplication         : Started CompositeApplication in 2.59 seconds (JVM running for 3.333)
2019-07-03 19:56:33.722 DEBUG 5420 --- [           main] stomAnnotationTransactionAttributeSource : Adding transactional method 'saveAll' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Hibernate: 
    select
        song0_.album as album1_0_0_,
        song0_.artist as artist2_0_0_,
        song0_.name as name3_0_0_,
        song0_.download_url as download4_0_0_,
        song0_.duration as duration5_0_0_,
        song0_.genre as genre6_0_0_,
        song0_.rating as rating7_0_0_,
        song0_.release_date as release_8_0_0_ 
    from
        composite.song song0_ 
    where
        song0_.album=? 
        and song0_.artist=? 
        and song0_.name=?
Hibernate: 
    insert 
    into
        composite.song
        (download_url, duration, genre, rating, release_date, album, artist, name) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    update
        composite.song 
    set
        download_url=?,
        duration=?,
        genre=?,
        rating=?,
        release_date=? 
    where
        album=? 
        and artist=? 
        and name=?
2019-07-03 19:56:33.773  INFO 5420 --- [       Thread-4] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2019-07-03 19:56:33.776  INFO 5420 --- [       Thread-4] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2019-07-03 19:56:33.779  INFO 5420 --- [       Thread-4] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Upvotes: 1

Views: 8093

Answers (2)

Avinash Gupta
Avinash Gupta

Reputation: 228

Spring Data Jpa Repository functionality is implemented via the SimpleJpaRepository class containing following save(..) method:

@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

Thus the Spring Jpa Data Repository save(...) method merges an already existing entity.

Opposed to that the naked EntityManager#persist() throws an exception if invoked with already existing entity.

The problem might be solved by adding custom behavior to Spring Data Repository/ies.

Alternatively : you can follow this

The easiest (and least invasive) way to work around this is probably by making sure the id only gets set right before the persist. This can be achieved in a @PrePersist callback:

@Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @Entity
    public class Song {
        @EmbeddedId
        private SongId id;

        private int duration;
        private String genre;
        private LocalDateTime releaseDate;
        private int rating;
        private String downloadUrl;

    @PrePersist
    void initIdentifier() {
        SongId songId1 = SongId.builder().name("John").album("AlbumA").artist("ArtistA").build();
        if (id == null) {
            this.id = … songId1 // Create ID instance here.
        }
    }
    }

Upvotes: 0

t4dohx
t4dohx

Reputation: 668

Change:

public interface SongsRepository extends JpaRepository<Song, Long>

to

public interface SongsRepository extends JpaRepository<Song, SongId>

Upvotes: 2

Related Questions