¿Cómo actualizo una entidad usando spring-data-jpa?

194

Bueno, la pregunta lo dice todo. Usando JPARepository, ¿cómo actualizo una entidad?

JPARepository solo tiene un método de guardar , que no me dice si es crear o actualizar en realidad. Por ejemplo, insertar un simple objeto al usuario de base de datos, que tiene tres campos: firstname, lastnamey 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;
  }
}

Luego simplemente llamo save(), que en este punto es en realidad una inserción en la base de datos:

 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);

Hasta aquí todo bien. Ahora quiero actualizar a este usuario, por ejemplo, cambiar su edad. Para este propósito, podría usar una consulta, QueryDSL o NamedQuery, lo que sea. Pero, considerando que solo quiero usar spring-data-jpa y el JPARepository, ¿cómo le digo que en lugar de una inserción quiero hacer una actualización?

Específicamente, ¿cómo le digo a spring-data-jpa que los usuarios con el mismo nombre de usuario y nombre son realmente IGUALES y que se supone que la entidad existente debe actualizarse? Anular iguales no resolvió este problema.

Eugene
fuente
1
¿Está seguro de que la ID se reescribe cuando guarda un objeto existente en la base de datos? Nunca tuve esto en mi proyecto tbh
Byron Voorbach
@ByronVoorbach, sí, tienes razón, acabo de probar esto. actualizar la pregunta también, gracias
Eugene
2
Hola amigo, puedes mirar este enlace stackoverflow.com/questions/24420572/… puedes ser un enfoque como saveOrUpdate ()
ibrahimKiraz
Creo que tenemos una solución hermosa aquí: ingrese la descripción del enlace aquí
Clebio Vieira

Respuestas:

207

La identidad de las entidades se define por sus claves principales. Dado que firstnamey lastnameno son partes de la clave primaria, no puede decirle a JPA que trate Users con los mismos firstnamesys lastnameiguales si tienen userIds diferentes .

Por lo tanto, si desea actualizar un Useridentificado por su firstnamey lastname, debe buscarlo Usermediante una consulta y luego cambiar los campos apropiados del objeto que encontró. Estos cambios se transferirán automáticamente a la base de datos al final de la transacción, por lo que no necesita hacer nada para guardar estos cambios explícitamente.

EDITAR:

Tal vez debería profundizar en la semántica general de JPA. Existen dos enfoques principales para el diseño de API de persistencia:

  • enfoque de inserción / actualización . Cuando necesite modificar la base de datos, debe llamar explícitamente a los métodos de API de persistencia: llama insertpara insertar un objeto o updatepara guardar un nuevo estado del objeto en la base de datos.

  • Enfoque de unidad de trabajo . En este caso, tiene un conjunto de objetos gestionados por la biblioteca de persistencia. Todos los cambios que realice en estos objetos se transferirán automáticamente a la base de datos al final de la Unidad de trabajo (es decir, al final de la transacción actual en el caso típico). Cuando necesita insertar un nuevo registro en la base de datos, hace que se gestione el objeto correspondiente . Los objetos administrados se identifican por sus claves primarias, de modo que si crea un objeto con clave primaria predefinida administrada , se asociará con el registro de la base de datos de la misma identificación, y el estado de este objeto se propagará a ese registro automáticamente.

JPA sigue el último enfoque. save()en Spring Data JPA está respaldado por merge()JPA simple, por lo tanto, hace que su entidad se administre como se describe anteriormente. Significa que llamar save()a un objeto con una identificación predefinida actualizará el registro de la base de datos correspondiente en lugar de insertar uno nuevo, y también explica por qué save()no se llama create().

axtavt
fuente
bueno si creo que lo se. Me refería estrictamente a spring-data-jpa. Ahora tengo dos problemas con esta respuesta: 1) no se supone que los valores comerciales sean parte de la clave principal; eso es algo conocido, ¿verdad? Por lo tanto, tener el nombre y el apellido como clave principal no es bueno. Y 2) ¿Por qué este método no se llama crear, sino guardar en spring-data-jpa?
Eugene
1
"save () en Spring Data JPA está respaldado por merge () en JPA simple" ¿realmente miró el código? Acabo de hacerlo y ambos respaldados por persistir o fusionarse. Persistirá o se actualizará en función de la presencia de la identificación (clave principal). Esto, creo, debería documentarse en el método de guardar. Así que guardar es, en realidad, fusionarse o persistir.
Eugene
Creo que también se llama guardar, porque se supone que guarda el objeto sin importar en qué estado se encuentre; realizará una actualización o inserción que es igual al estado de guardar.
Eugene
1
Esto no funcionará para mí. He intentado guardar en una entidad con una clave primaria válida. Accedo a la página con "order / edit /: id" y en realidad me da el objeto correcto por Id. Nada de lo que intento por el amor de Dios actualizará la entidad. Siempre publica una nueva entidad ... Incluso intenté hacer un servicio personalizado y usar "fusionar" con mi EntityManager y todavía no funciona. Siempre publicará una nueva entidad.
DtechNet
1
@DTechNet Estaba experimentando un problema similar para usted, DtechNet, y resultó que mi problema era que tenía el tipo de clave principal incorrecto especificado en mi interfaz de repositorio de Spring Data. Dijo en extends CrudRepository<MyEntity, Integer>lugar de extends CrudRepository<MyEntity, String>como debería haberlo hecho. ¿Eso ayuda? Sé que esto es casi un año después. Espero que ayude a alguien más.
Kent Bull
139

Dado que la respuesta de @axtavt se centra en JPAnospring-data-jpa

Para actualizar una entidad mediante consultas, entonces guardar no es eficiente porque requiere dos consultas y posiblemente la consulta puede ser bastante costosa ya que puede unir otras tablas y cargar cualquier colección que tenga fetchType=FetchType.EAGER

Spring-data-jpaadmite la operación de actualización.
Debe definir el método en la interfaz del repositorio y anotarlo con @Queryy @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);

@Queryes para la definición de consulta personalizada y @Modifyinges por decir spring-data-jpaque esta consulta es una operación de actualización y requiere executeUpdate()no executeQuery().

Puede especificar otros tipos de devolución:
int- el número de registros que se actualizan.
boolean- verdadero si hay un registro que se está actualizando. De lo contrario, falso.


Nota : Ejecute este código en una transacción .

hussachai
fuente
10
Asegúrese de ejecutarlo en la transacción
hussachai
1
¡Oye! Gracias estoy usando frijoles de datos de primavera. Por lo tanto, se encargará automáticamente de mi actualización. <S extiende T> S guardar (entidad S); se encarga automáticamente de la actualización. ¡No tuve que usar tu método! ¡Gracias de todos modos!
bks4line
3
En cualquier momento :) El método de guardar funciona si desea guardar la entidad (delegará la llamada a em.persist () o em.merge () detrás de escena). De todos modos, la consulta personalizada es útil cuando desea actualizar solo algunos campos de la base de datos.
hussachai
¿qué tal cuando uno de sus parámetros es id de sub entidad (manyToOne) cómo debería actualizar eso? (el libro tiene un Autor y usted pasó la identificación del libro y la identificación del autor para actualizar el autor del libro)
Mahdi
1
To update an entity by querying then saving is not efficientEstas no son las dos únicas opciones. Hay una manera de especificar id y obtener el objeto de fila sin consultarlo. Si hace un row = repo.getOne(id)y luego row.attr = 42; repo.save(row);y mira los registros, verá solo la consulta de actualización.
nurettin
21

Simplemente puede usar esta función con la función save () JPA, pero el objeto enviado como parámetro debe contener una identificación existente en la base de datos; de lo contrario, no funcionará, porque save () cuando enviamos un objeto sin identificación, agrega directamente una fila en base de datos, pero si enviamos un objeto con una identificación existente, cambia las columnas que ya se encuentran en la base de datos.

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
fuente
44
¿No es un problema el rendimiento si tiene que cargar el objeto desde la base de datos antes de realizar la actualización? (lo siento por mi inglés)
agosto0490
3
hay dos consultas en lugar de una, lo cual es altamente no
preferido
¡Sé que simplemente apareció otro método para hacer! pero ¿por qué el jpa implementó la función de actualización cuando la identificación es la misma?
Kalifornium
17

Como lo que ya han mencionado otros, el save()mismo contiene operaciones de creación y actualización.

Solo quiero agregar un suplemento sobre lo que hay detrás del save()método.

En primer lugar, veamos la jerarquía extender / implementar de CrudRepository<T,ID>, ingrese la descripción de la imagen aquí

Ok, verifiquemos la save()implementación en 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 puede ver, verificará si la ID existe o no en primer lugar, si la entidad ya está allí, solo se realizará la actualización por merge(entity)método y, de lo contrario, se insertará un nuevo registro por persist(entity)método.

Eugene
fuente
7

Usando spring-data-jpa save(), estaba teniendo el mismo problema que @DtechNet. Quiero decir que cada uno save()estaba creando un nuevo objeto en lugar de una actualización. Para resolver esto, tuve que agregar el versioncampo a la entidad y la tabla relacionada.

sonreír
fuente
lo que quiere decir con agregar un campo de versión a la entidad y la tabla relacionada.
jcrshankar
5

Así es como resolví el problema:

User inbound = ...
User existing = userRepository.findByFirstname(inbound.getFirstname());
if(existing != null) inbound.setId(existing.getId());
userRepository.save(inbound);
Adán
fuente
Utilice el @Transactionmétodo anterior para varias solicitudes de db. Y en este caso no es necesario userRepository.save(inbound);, los cambios se enjuagan automáticamente.
Grigory Kislin
5

El save()método Spring Data lo ayudará a realizar ambas cosas: agregar un nuevo elemento y actualizar un elemento existente.

Solo llama al save()y disfruta de la vida :))

Amir Mhp
fuente
De esta manera, si envié diferente Idlo guardaré, ¿cómo puedo evitar guardar un nuevo registro?
Abd Abughazaleh
1
@AbdAbughazaleh comprueba si existe un Id entrante en tu repositorio o no. puede usar 'repository.findById (id) .map (entidad -> {// hacer algo devolver repository.save (entidad)}) .orElseGet (() -> {// hacer algo devolver;}); '
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
fuente
@JoshuaTaylor de hecho, se lo perdió por completo :) eliminará el comentario ...
Eugene
1

Específicamente, ¿cómo le digo a spring-data-jpa que los usuarios que tienen el mismo nombre de usuario y nombre son realmente IGUALES y que se supone que deben actualizar la entidad? Reemplazar iguales no funcionó.

Para este propósito particular, se puede introducir una clave compuesta 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`))

Cartografía:

@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
}

Aquí está cómo usarlo:

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

    private String description;

    //...
}

JpaRepository se vería así:

public interface UserEntityRepository extends JpaRepository<UserEntity, UserKey>

Luego, puede usar el siguiente modismo: acepte DTO con información de usuario, extraiga el nombre y el nombre y cree UserKey, luego cree una UserEntity con esta clave compuesta y luego invoque Spring Data save (), que debería resolver todo por usted.

Andreas Gelever
fuente