JPA problem record

Last month trial under the JPA, found some strange problems, record it.

版本:spring-boot-starter-data-jpa:2.2.0.RELEASE,

MySQL: 8.0.16, InnoDB engine, RR isolation level.

0.Initial data

Entity classes:

@Data
@Entity
@Table(name = "people")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class People {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "username")
    private String username;
}

@Data
@Entity
@Table(name = "cat")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Cat {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "name")
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "owner")
    private People owner;
}
复制代码

Table data:

mysql> select * from people;
+----+----------+
| id | username |
+----+----------+
|  1 | zhangsan |
|  2 | laoli    |
+----+----------+
2 rows in set (0.00 sec)

mysql> select * from cat;
+----+-------+-------+
| id | name  | owner |
+----+-------+-------+
|  1 | kitty |     1 |
|  2 | mao   |     2 |
|  3 | mi    |     2 |
+----+-------+-------+
3 rows in set (0.00 sec)
复制代码

The latest reading is less than the value of 1. After updating the data in the same transaction

Test code is as follows:

public interface PeopleRepo extends JpaRepository<People, Integer>, JpaSpecificationExecutor<People> {
    @Transactional
    @Modifying
    @Query(value = "update people set username = :username where id = :id", nativeQuery = true)
    void updateUsernameById(@Param("id") int id, @Param("username") String username);
}

@Service
public class PeopleAndCatService {
    @Autowired
    private PeopleRepo peopleRepo;

    @Transactional
    public String test1(int id) {
        People p1 = peopleRepo.findById(id).get()			// 1
        System.out.println("p1: " + p1);
        peopleRepo.updateUsernameById(id, "ceeeeb");	// 2
        People p2 = peopleRepo.findById(id).get()			// 3
        System.out.println("p2: " + p2);
        return null;
    }
}
复制代码

When the call interface test1 (1), returns the following results:

p1: People(id=1, username=zhangsan)
p2: People(id=1, username=zhangsan)
复制代码

But the data in the database has changed.

The reason: a transaction (Propagation.REQUIRED mode) corresponds to a EntityManager. When the query execution step 1, EntityManager] [SessionImpl entity class already exists in the cache id = 1, Step 3 query again, available directly from the cache, not actually query the database.

Solution (Reference) :

  • Step 2 sql handwriting was changed to save (S entity) JpaRepository provides a method;
  • Or after step 2, the emptying of the session cache @Modifying(clearAutomatically = true).

2. lazy loading caused by org.hibernate.LazyInitializationException: could not initialize proxy - no Sessionerrors

Test code:

@Service
public class PeopleAndCatService {
    @Autowired
    private CatRepo catRepo;
    
    @Transactional
    public Cat test2(int id) {
        Cat c = catRepo.findById(id).get();	// 1
        return c;
    }
}

@RestController
@RequestMapping("/jpa")
public class PeopleAndCatController {
    @Autowired
    private PeopleAndCatService peopleAndCatService;

    @GetMapping("/test2")
    public Cat test2(@RequestParam("id") int id) {
        Cat cat = peopleAndCatService.test2(id);
        return cat;		// 2
    }
}
复制代码

The reason is that the error: Final returns the response time of the controller inside, jackson Cat serialized object fails, more precisely, the object is serialized Cat inside when the owner field error. owner field is a People object when since we are lazy loading, so step 1 query, the actual sql statement is select * from cat where id = ?, and is not associated with the query People table (but get id owner of a).

When 2:00, you need to perform a sequence of steps People object, call people.getUsername () method, it will trigger a database query (lazy loading!), But this time session is closed, and therefore error.

Solution:

  • No lazy loading, change @ManyToOne(fetch = FetchType.EAGER);

  • When the transaction is not completed, such as steps 1 back, call c.getOwner().getUsername()triggers a query People operating table;

  • The above program eventually queries the People table, if I did not care owner, Charles People do not want to watch it; can be rewritten as follows:

    @Transactional
    public Cat test2(int id) {
        Cat c = catRepo.findById(id).get();
        People people = new People();
        people.setId(c.getOwner().getId());
        c.setOwner(people);
        return c;
    }
    复制代码
  • Without foreign keys, change the owner of the Cat class field of type int, requested in writing sql query when it comes to association, association between the tables controlled by the program realization (related to database design in dispute whether the foreign key of the ...)

Guess you like

Origin juejin.im/post/5dc3a5505188251a791a51a4