Como atualizo uma entidade usando spring-data-jpa?

194

Bem, a pergunta praticamente diz tudo. Usando o JPARepository, como atualizo uma entidade?

O JPARepository possui apenas um método de salvamento , que não informa se é realmente criado ou atualizado. Por exemplo, eu inserir um simples objeto para o usuário do banco de dados, que tem três campos: firstname, lastnamee age:

 @Entity
 public class User {

  private String firstname;
  private String lastname;
  //Setters and getters for age omitted, but they are the same as with firstname and lastname.
  private int age;

  @Column
  public String getFirstname() {
    return firstname;
  }
  public void setFirstname(String firstname) {
    this.firstname = firstname;
  }

  @Column
  public String getLastname() {
    return lastname;
  }
  public void setLastname(String lastname) {
    this.lastname = lastname;
  }

  private long userId;

  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  public long getUserId(){
    return this.userId;
  }

  public void setUserId(long userId){
    this.userId = userId;
  }
}

Então eu simplesmente chamo save(), que neste momento é realmente uma inserção no banco de dados:

 User user1 = new User();
 user1.setFirstname("john"); user1.setLastname("dew");
 user1.setAge(16);

 userService.saveUser(user1);// This call is actually using the JPARepository: userRepository.save(user);

Por enquanto, tudo bem. Agora eu quero atualizar este usuário, digamos, mudar sua idade. Para esse propósito, eu poderia usar uma consulta, QueryDSL ou NamedQuery, qualquer que seja. Mas, considerando que eu só quero usar o spring-data-jpa e o JPARepository, como posso dizer que, em vez de uma inserção, quero fazer uma atualização?

Especificamente, como digo ao spring-data-jpa que os usuários com o mesmo nome de usuário e nome são realmente iguais e que a entidade existente deveria ser atualizada? Substituir iguais não resolveu esse problema.

Eugene
fonte
1
Tem certeza de que o ID é reescrito quando você salva um objeto existente no banco de dados? Nunca tive isso no meu projeto tbh
Byron Voorbach
@ByronVoorbach, você está certo, apenas testei isso. atualizar a questão também, thx
Eugene
2
Olá amigo, você pode olhar este link stackoverflow.com/questions/24420572/... pode ser uma abordagem como saveOrUpdate ()
ibrahimKiraz
Eu acho que temos uma solução bonita aqui: digite a descrição do link aqui
Clebio Vieira 10/06

Respostas:

207

A identidade das entidades é definida por suas chaves primárias. Desde firstnamee lastnamenão são partes da chave primária, você não pode dizer JPA para deleite Users com os mesmos firstnames e lastnames como iguais se eles têm diferentes userIds.

Portanto, se você quiser atualizar um Useridentificado por its firstnamee lastname, precisará encontrá-lo Userpor uma consulta e depois alterar os campos apropriados do objeto encontrado. Essas alterações serão liberadas no banco de dados automaticamente no final da transação, para que você não precise fazer nada para salvar essas alterações explicitamente.

EDITAR:

Talvez eu deva elaborar a semântica geral da JPA. Existem duas abordagens principais para o design de APIs de persistência:

  • abordagem de inserção / atualização . Quando você precisar modificar o banco de dados, chame métodos de API de persistência explicitamente: ligue insertpara inserir um objeto ou updatesalve o novo estado do objeto no banco de dados.

  • Abordagem por unidade de trabalho . Nesse caso, você tem um conjunto de objetos gerenciados pela biblioteca de persistência. Todas as alterações feitas nesses objetos serão liberadas no banco de dados automaticamente no final da Unidade de trabalho (ou seja, no final da transação atual, no caso típico). Quando você precisa inserir um novo registro no banco de dados, você gerencia o objeto correspondente . Os objetos gerenciados são identificados por suas chaves primárias, para que, se você criar um objeto com chave primária predefinida gerenciada , ele será associado ao registro do banco de dados do mesmo ID e o estado desse objeto será propagado automaticamente para esse registro.

A JPA segue a última abordagem. save()no Spring Data, o JPA é suportado pelo merge()JPA comum, portanto, ele gerencia sua entidade como descrito acima. Isso significa que a chamada save()de um objeto com ID predefinido atualizará o registro do banco de dados correspondente em vez de inserir um novo e também explica por que save()não é chamado create().

axtavt
fonte
Bem, sim, acho que sei disso. Eu estava me referindo estritamente ao spring-data-jpa. Agora tenho dois problemas com esta resposta: 1) os valores de negócios não devem fazer parte da chave primária - isso é uma coisa conhecida, certo? Portanto, não é bom ter o nome e o sobrenome como chave primária. E 2) Por que esse método não é chamado de criação, mas salva no spring-data-jpa?
Eugene
1
"save () no Spring Data JPA é apoiado por merge () no JPA comum" você realmente olhou o código? Acabei de fazer e ambos os backups persistiram ou se fundiram. Ele persistirá ou será atualizado com base na presença do ID (chave primária). Acho que isso deve ser documentado no método save. Portanto, salvar é realmente mesclar ou persistir.
Eugene
Eu acho que também é chamado de salvar, porque ele deve salvar o objeto, não importa em que estado ele esteja - ele fará uma atualização ou inserção que é igual ao estado de salvamento.
Eugene
1
Isso não vai funcionar para mim. Eu tentei salvar em uma entidade com uma chave primária válida. Eu acesso a página com "order / edit /: id" e ela realmente me fornece o objeto correto por Id. Nada que eu tente pelo amor de Deus atualizará a entidade. Ele sempre publica uma nova entidade. Eu até tentei fazer um serviço personalizado e usar "mesclar" com meu EntityManager e ainda não funcionará. Ele sempre publicará uma nova entidade.
DtechNet 15/08/2015
1
@ DTechNet Eu estava enfrentando um problema semelhante ao seu, DtechNet, e o problema era que eu tinha o tipo de chave primária errado especificado na minha interface do repositório do Spring Data. Disse em extends CrudRepository<MyEntity, Integer>vez de extends CrudRepository<MyEntity, String>como deveria. Isso ajuda? Eu sei que isso é quase um ano depois. Espero que isso ajude mais alguém.
Kent Touro
139

Como a resposta de @axtavt se concentra em JPAnãospring-data-jpa

Para atualizar uma entidade consultando, o salvamento não é eficiente, pois requer duas consultas e, possivelmente, a consulta pode ser bastante cara, pois pode ingressar em outras tabelas e carregar quaisquer coleções que tenham fetchType=FetchType.EAGER

Spring-data-jpa suporta operação de atualização.
Você precisa definir o método na interface do repositório. E anotou-o com @Querye @Modifying.

@Modifying
@Query("update User u set u.firstname = ?1, u.lastname = ?2 where u.id = ?3")
void setUserInfoById(String firstname, String lastname, Integer userId);

@Query é para definir consulta personalizada e @Modifying é para dizer spring-data-jpaque esta consulta é uma operação de atualização e que exige executeUpdate()não executeQuery().

Você pode especificar outros tipos de retorno:
int - o número de registros que estão sendo atualizados.
boolean- true se houver um registro sendo atualizado. Caso contrário, false.


Nota : Execute este código em uma transação .

hussachai
fonte
10
Certifique-se de executá-lo na transação
hussachai
1
Ei! Obrigado, estou usando data beans de primavera. Por isso, ele cuida automaticamente da minha atualização. <S estende T> S save (entidade S); cuida automaticamente da atualização. Eu não precisava usar seu método! Obrigado de qualquer forma!
precisa saber é o seguinte
3
A qualquer momento :) O método save funciona se você deseja salvar a entidade (delegará a chamada para em.persist () ou em.merge () nos bastidores). De qualquer forma, a consulta personalizada é útil quando você deseja atualizar apenas alguns campos no banco de dados.
hussachai
e quando um de seus parâmetros é o ID da subentidade (manyToOne), como deve atualizar isso? (o livro tem um autor e você passou o ID do livro e o ID do autor para atualizar o autor do livro)
Mahdi
1
To update an entity by querying then saving is not efficientessas não são as únicas duas opções. Existe uma maneira de especificar a identificação e obter o objeto de linha sem consultá-lo. Se você fizer um row = repo.getOne(id)e depois row.attr = 42; repo.save(row);assistir os logs, verá apenas a consulta de atualização.
Nurettin
21

Você pode simplesmente usar essa função com save () JPAfunction, mas o objeto enviado como parâmetro deve conter um ID existente no banco de dados, caso contrário não funcionará, porque save () quando enviamos um objeto sem ID, ele adiciona diretamente uma linha no banco de dados, mas se enviarmos um objeto com um ID existente, ele mudará as colunas já encontradas no banco de dados.

public void updateUser(Userinfos u) {
    User userFromDb = userRepository.findById(u.getid());
    // crush the variables of the object found
    userFromDb.setFirstname("john"); 
    userFromDb.setLastname("dew");
    userFromDb.setAge(16);
    userRepository.save(userFromDb);
}
Kalifornium
fonte
4
o desempenho não é um problema se você precisar carregar o objeto do banco de dados antes da atualização? (desculpe pelo meu inglês)
august0490
3
há duas consultas em vez de uma, que não é altamente
preferível
Eu sei que Juste apareceu outro método para fazer! mas por que o jpa implementou a função de atualização quando o ID é o mesmo?
Kalifornium
17

Como o que já foi mencionado por outros, o save() próprio contém operações de criação e atualização.

Eu só quero adicionar um complemento sobre o que está por trás do save() método.

Primeiro, vamos ver a hierarquia de extensão / implementação do CrudRepository<T,ID>, insira a descrição da imagem aqui

Ok, vamos verificar a save()implementação em SimpleJpaRepository<T, ID>,

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

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

Como você pode ver, ele verificará se o ID existe ou não, primeiro, se a entidade já estiver lá, apenas a atualização ocorrerá por merge(entity)método e, caso contrário, um novo registro será inserido por persist(entity)método.

Eugene
fonte
7

Usando spring-data-jpa save(), eu estava tendo o mesmo problema que o @DtechNet. Quero dizer que todos save()estavam criando um novo objeto em vez de atualizar. Para resolver isso, tive que adicionar um versioncampo à entidade e à tabela relacionada.

sorriso
fonte
o que você quer dizer com adicionar campo de versão à entidade e tabela relacionada.
jcrshankar 2/03
5

Foi assim que resolvi o problema:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
Adão
fonte
Use o @Transactionmétodo acima para várias solicitações de banco de dados. Nesse caso, não há necessidade de userRepository.save(inbound);alterações automaticamente.
Grigory Kislin
5

O save()método de dados spring ajudará você a executar os dois: adicionar novo item e atualizar um item existente.

Basta ligar para save()e aproveitar a vida :))

Amir Mhp
fonte
Dessa forma, se eu enviei um arquivo diferente Id, ele será salvo, como posso evitar salvar um novo registro.
Abd Abughazaleh 28/04
1
O @AbdAbughazaleh verifica se o ID recebido existe no seu repositório ou não. você pode usar 'repository.findById (id) .map (entidade -> {// fazer algo retornar repository.save (entidade)}). ouElseGet (() -> {// fazer algo retornar;}); '
Amir Mhp
1
public void updateLaserDataByHumanId(String replacement, String humanId) {
    List<LaserData> laserDataByHumanId = laserDataRepository.findByHumanId(humanId);
    laserDataByHumanId.stream()
            .map(en -> en.setHumanId(replacement))
            .collect(Collectors.toList())
            .forEach(en -> laserDataRepository.save(en));
}
BinLee
fonte
@JoshuaTaylor de fato, perdeu essa inteiramente :) irá remover o comentário ...
Eugene
1

Especificamente, como eu digo ao spring-data-jpa que os usuários que têm o mesmo nome de usuário e nome são realmente iguais e que ele deve atualizar a entidade. Substituir iguais não funcionou.

Para este fim específico, pode-se introduzir uma chave composta como esta:

CREATE TABLE IF NOT EXISTS `test`.`user` (
  `username` VARCHAR(45) NOT NULL,
  `firstname` VARCHAR(45) NOT NULL,
  `description` VARCHAR(45) NOT NULL,
  PRIMARY KEY (`username`, `firstname`))

Mapeamento:

@Embeddable
public class UserKey implements Serializable {
    protected String username;
    protected String firstname;

    public UserKey() {}

    public UserKey(String username, String firstname) {
        this.username = username;
        this.firstname = firstname;
    }
    // equals, hashCode
}

Aqui está como usá-lo:

@Entity
public class UserEntity implements Serializable {
    @EmbeddedId
    private UserKey primaryKey;

    private String description;

    //...
}

JpaRepository ficaria assim:

public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>

Então, você pode usar o seguinte idioma: aceite o DTO com informações do usuário, extraia o nome e o primeiro nome e crie UserKey, crie uma UserEntity com essa chave composta e invoque o Spring Data save () que deve resolver tudo para você.

Andreas Gelever
fonte