Khi sử dụng các phương thức getOne và findOne Spring Data JPA


154

Tôi có một trường hợp sử dụng mà nó gọi như sau:

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl getUserControlById(Integer id){
    return this.userControlRepository.getOne(id);
}

Quan sát @TransactionalPropagation.REQUIRES_NEW và kho lưu trữ sử dụng getOne . Khi tôi chạy ứng dụng, tôi nhận được thông báo lỗi sau:

Exception in thread "main" org.hibernate.LazyInitializationException: 
could not initialize proxy - no Session
...

Nhưng nếu tôi thay đổi getOne(id)bởi findOne(id)tất cả hoạt động tốt.

BTW, ngay trước trường hợp sử dụng gọi phương thức getUserControlById , nó đã được gọi là phương thức insertUserControl

@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public UserControl insertUserControl(UserControl userControl) {
    return this.userControlRepository.save(userControl);
}

Cả hai phương pháp đều là Propagation.REQUIRES_NEW vì tôi đang thực hiện kiểm soát kiểm toán đơn giản .

Tôi sử dụng getOnephương thức này vì nó được định nghĩa trong giao diện JpaRep repository và giao diện Kho lưu trữ của tôi kéo dài từ đó, tất nhiên tôi đang làm việc với JPA.

Các JpaRepository giao diện kéo dài từ CrudRepository . Các findOne(id)phương pháp được quy định tạiCrudRepository .

Câu hỏi của tôi là:

  1. Tại sao thất bại getOne(id)phương pháp?
  2. Khi nào tôi nên sử dụng getOne(id)phương pháp?

Tôi đang làm việc với các kho lưu trữ khác và tất cả đều sử dụng getOne(id)phương thức này và tất cả đều hoạt động tốt, chỉ khi tôi sử dụng Tuyên truyền.REQUIRES_NEW thì không thành công.

Theo API getOne :

Trả về một tham chiếu đến thực thể với mã định danh đã cho.

Theo API findOne :

Lấy một thực thể bằng id của nó.

3) Khi nào tôi nên sử dụng findOne(id) phương pháp?

4) Phương pháp nào được khuyến nghị sử dụng?

Cảm ơn trước.


Bạn đặc biệt không nên sử dụng getOne () để kiểm tra sự tồn tại của một đối tượng trong cơ sở dữ liệu, bởi vì với getOne, bạn luôn nhận được một đối tượng! = Null, trong khi findOne cung cấp null.
Uwe Allner

Câu trả lời:


135

TL; DR

T findOne(ID id)(tên trong API cũ) / Optional<T> findById(ID id)(tên trong API mới) phụ thuộc vào EntityManager.find()việc thực hiện tải thực thể háo hức .

T getOne(ID id)dựa vào EntityManager.getReference()đó thực hiện một tải thực thể lười biếng . Vì vậy, để đảm bảo tải thực thể hiệu quả, yêu cầu một phương thức trên nó là bắt buộc.

findOne()/findById()thực sự rõ ràng và đơn giản hơn để sử dụng hơn getOne().
Vì vậy, trong hầu hết các trường hợp, ủng hộ findOne()/findById()hơn getOne().


Thay đổi API

Từ ít nhất, 2.0phiên bản, Spring-Data-Jpasửa đổi findOne().
Trước đây, nó được định nghĩa trong CrudRepositorygiao diện là:

T findOne(ID primaryKey);

Bây giờ, findOne()phương thức duy nhất mà bạn sẽ tìm thấy CrudRepositorylà phương thức được xác định trong QueryByExampleExecutorgiao diện là:

<S extends T> Optional<S> findOne(Example<S> example);

Điều đó được thực hiện cuối cùng bởi SimpleJpaRepository, việc thực hiện mặc định của CrudRepositorygiao diện.
Phương pháp này là một truy vấn bằng cách tìm kiếm ví dụ và bạn không muốn thay thế nó.

Trên thực tế, phương thức có hành vi tương tự vẫn có trong API mới nhưng tên phương thức đã thay đổi.
Nó đã được đổi tên từ findOne()thành findById()trong CrudRepositorygiao diện:

Optional<T> findById(ID id); 

Bây giờ nó trả về một Optional. Mà không phải là quá xấu để ngăn chặn NullPointerException.

Vì vậy, sự lựa chọn thực tế là giữa Optional<T> findById(ID id)T getOne(ID id).


Hai phương thức riêng biệt dựa trên hai phương thức truy xuất JPA EntityManager riêng biệt

1) Optional<T> findById(ID id)javadoc nói rằng:

Lấy một thực thể bằng id của nó.

Khi chúng ta xem xét việc thực hiện, chúng ta có thể thấy rằng nó dựa vào EntityManager.find()để thực hiện truy xuất:

public Optional<T> findById(ID id) {

    Assert.notNull(id, ID_MUST_NOT_BE_NULL);

    Class<T> domainType = getDomainClass();

    if (metadata == null) {
        return Optional.ofNullable(em.find(domainType, id));
    }

    LockModeType type = metadata.getLockModeType();

    Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();

    return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}

Và đây em.find()là một EntityManagerphương thức được khai báo là:

public <T> T find(Class<T> entityClass, Object primaryKey,
                  Map<String, Object> properties);

Javadoc của nó tuyên bố:

Tìm theo khóa chính, sử dụng các thuộc tính được chỉ định

Vì vậy, việc truy xuất một thực thể được tải dường như được mong đợi.

2) Trong khi các trạng thái T getOne(ID id)javadoc (nhấn mạnh là của tôi):

Trả về một tham chiếu đến thực thể với mã định danh đã cho.

Trên thực tế, thuật ngữ tham chiếu thực sự là hội đồng quản trị và API JPA không chỉ định bất kỳ getOne()phương pháp nào .
Vì vậy, điều tốt nhất để làm để hiểu những gì trình bao bọc Spring làm là xem xét việc thực hiện:

@Override
public T getOne(ID id) {
    Assert.notNull(id, ID_MUST_NOT_BE_NULL);
    return em.getReference(getDomainClass(), id);
}

Đây em.getReference()là một EntityManagerphương thức được khai báo là:

public <T> T getReference(Class<T> entityClass,
                              Object primaryKey);

Và may mắn thay, EntityManagerjavadoc xác định rõ hơn ý định của nó (nhấn mạnh là của tôi):

Lấy một ví dụ, trạng thái có thể được lấy một cách lười biếng . Nếu phiên bản được yêu cầu không tồn tại trong cơ sở dữ liệu, EntityNotFoundException sẽ bị ném khi trạng thái thể hiện được truy cập lần đầu tiên . (Thời gian chạy của nhà cung cấp kiên trì được phép ném EntityNotFoundException khi getReference được gọi.) Ứng dụng không nên hy vọng rằng trạng thái cá thể sẽ có sẵn khi tách ra , trừ khi ứng dụng được truy cập trong khi trình quản lý thực thể được mở.

Vì vậy, gọi getOne()có thể trả lại một thực thể tìm nạp lười biếng.
Ở đây, việc tìm nạp lười biếng không đề cập đến các mối quan hệ của thực thể mà là chính thực thể đó.

Điều đó có nghĩa là nếu chúng ta gọi getOne()và sau đó bối cảnh Kiên trì bị đóng, thực thể có thể không bao giờ được tải và do đó kết quả thực sự không thể đoán trước.
Ví dụ: nếu đối tượng proxy được tuần tự hóa, bạn có thể nhận được một nulltham chiếu là kết quả được tuần tự hóa hoặc nếu một phương thức được gọi trên đối tượng proxy, một ngoại lệ như LazyInitializationExceptionđược ném ra.
Vì vậy, trong tình huống này, việc ném EntityNotFoundExceptionnó là lý do chính để sử dụnggetOne() để xử lý một trường hợp không tồn tại trong cơ sở dữ liệu vì tình huống lỗi có thể không bao giờ được thực hiện trong khi thực thể không tồn tại.

Trong mọi trường hợp, để đảm bảo tải của nó, bạn phải thao tác thực thể trong khi phiên được mở. Bạn có thể làm điều đó bằng cách gọi bất kỳ phương thức nào trên thực thể.
Hoặc sử dụng thay thế tốt hơn thay findById(ID id)vì.


Tại sao một API không rõ ràng như vậy?

Để kết thúc, hai câu hỏi dành cho nhà phát triển Spring-Data-JPA:

  • Tại sao không có một tài liệu rõ ràng hơn cho getOne()? Thực thể lười tải thực sự không phải là một chi tiết.

  • Tại sao bạn cần phải giới thiệu getOne()để bọc EM.getReference()?
    Tại sao không chỉ đơn giản là dính vào phương pháp bọc : getReference()? Phương pháp EM này thực sự rất đặc biệt trong khi getOne() truyền tải một quá trình xử lý đơn giản như vậy.


3
Tôi đã bối rối tại sao getOne () không ném EntityNotFoundException, nhưng "EntityNotFoundException của bạn bị ném khi trạng thái thể hiện được truy cập lần đầu" giải thích cho tôi khái niệm. Cảm ơn
TheCoder

Tóm tắt câu trả lời này: getOne()sử dụng tải lười biếng và ném EntityNotFoundExceptionnếu không tìm thấy mục nào. findById()tải ngay lập tức và trả về null nếu không tìm thấy. Vì có một số tình huống không thể đoán trước với getOne (), nên sử dụng findById () để thay thế.
Janac Meena

124

Sự khác biệt cơ bản getOnelà lười tải và findOnekhông.

Hãy xem xét ví dụ sau:

public static String NON_EXISTING_ID = -1;
...
MyEntity getEnt = myEntityRepository.getOne(NON_EXISTING_ID);
MyEntity findEnt = myEntityRepository.findOne(NON_EXISTING_ID);

if(findEnt != null) {
     findEnt.getText(); // findEnt is null - this code is not executed
}

if(getEnt != null) {
     getEnt.getText(); // Throws exception - no data found, BUT getEnt is not null!!!
}

1
không tải lười biếng có nghĩa là nó sẽ chỉ được tải khi thực thể sẽ được sử dụng? Vì vậy, tôi mong đợi getEnt là null và mã bên trong thứ hai nếu không được thực thi. Bạn có thể giải thích. Cảm ơn!
Doug

Nếu được bao bọc bên trong một dịch vụ web CompleteableFuture <> tôi đã thấy rằng bạn sẽ muốn sử dụng findOne () so với getOne () vì việc triển khai lười biếng.
Fratt

76

1. Tại sao phương thức getOne (id) không thành công?

Xem phần này trong các tài liệu . Bạn ghi đè lên giao dịch đã có sẵn có thể gây ra sự cố. Tuy nhiên, không có thêm thông tin thì điều này rất khó trả lời.

2. Khi nào tôi nên sử dụng phương thức getOne (id)?

Không đào sâu vào phần bên trong của Spring Data JPA, sự khác biệt dường như nằm ở cơ chế được sử dụng để truy xuất thực thể.

Nếu bạn nhìn vào javadoc cho getOne(ID)dưới See Also :

See Also:
EntityManager.getReference(Class, Object)

có vẻ như phương pháp này chỉ ủy thác cho việc triển khai của người quản lý thực thể JPA.

Tuy nhiên, các tài liệu cho findOne(ID)không đề cập đến điều này.

Manh mối cũng nằm trong tên của các kho lưu trữ. JpaRepositorylà JPA cụ thể và do đó có thể ủy quyền các cuộc gọi cho người quản lý thực thể nếu cần. CrudRepositorylà bất khả tri của công nghệ kiên trì được sử dụng. Nhìn đây . Nó được sử dụng làm giao diện đánh dấu cho nhiều công nghệ bền bỉ như JPA, Neo4J v.v.

Vì vậy, thực sự không có "sự khác biệt" trong hai phương pháp cho các trường hợp sử dụng của bạn, nó chỉ findOne(ID)chung chung hơn là chuyên biệt hơn getOne(ID). Cái nào bạn sử dụng là tùy thuộc vào bạn và dự án của bạn, nhưng cá nhân tôi sẽ gắn bó findOne(ID)vì nó làm cho mã của bạn ít được triển khai cụ thể hơn và mở ra cánh cửa để chuyển sang những thứ như MongoDB, v.v. trong tương lai mà không cần tái cấu trúc quá nhiều :)


Cảm ơn bạn Donovan, có ý nghĩa trả lời của bạn.
Manuel Jordan

20
Tôi nghĩ rằng rất sai lầm khi nói rằng there's not really a 'difference' in the two methodsở đây, bởi vì thực sự có một sự khác biệt lớn trong cách thức thực thể được lấy và những gì bạn nên mong đợi phương thức trở lại. Câu trả lời tiếp theo của @davidxxx làm nổi bật điều này rất rõ và tôi nghĩ rằng tất cả những ai sử dụng Spring Data JPA nên nhận thức được điều này. Nếu không, nó có thể gây ra khá nhiều đau đầu.
fridberg

16

Các getOnephương thức chỉ trả về tham chiếu từ DB (tải chậm). Vì vậy, về cơ bản, bạn ở ngoài giao dịch ( Transactionalbạn đã được khai báo trong lớp dịch vụ không được xem xét) và xảy ra lỗi.


Có vẻ EntityManager.getReference (Lớp, Đối tượng) trả về "không có gì" vì chúng tôi đang ở trong phạm vi Giao dịch mới.
Manuel Jordan

2

Tôi thực sự thấy rất khó khăn từ các câu trả lời trên. Từ quan điểm gỡ lỗi, tôi gần như đã dành 8 giờ để biết lỗi lầm ngớ ngẩn.

Tôi đã thử nghiệm dự án spring + hibernate + dozer + Mysql. Để được rõ ràng.

Tôi có Thực thể người dùng, Thực thể sách. Bạn làm các phép tính của ánh xạ.

Nhiều cuốn sách được gắn với một người dùng. Nhưng trong UserServiceImpl tôi đã cố gắng tìm nó bằng getOne (userId);

public UserDTO getById(int userId) throws Exception {

    final User user = userDao.getOne(userId);

    if (user == null) {
        throw new ServiceException("User not found", HttpStatus.NOT_FOUND);
    }
    userDto = mapEntityToDto.transformBO(user, UserDTO.class);

    return userDto;
}

Kết quả còn lại là

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 1,
        "name": "TEST_ME",
        "bookList": null
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

}

Đoạn mã trên không lấy được những cuốn sách mà người dùng đọc.

BookList luôn rỗng vì getOne (ID). Sau khi thay đổi thành findOne (ID). Kết quả là

{
"collection": {
    "version": "1.0",
    "data": {
        "id": 0,
        "name": "Annama",
        "bookList": [
            {
                "id": 2,
                "book_no": "The karma of searching",
            }
        ]
    },
    "error": null,
    "statusCode": 200
},
"booleanStatus": null

}


-1

trong khi spring.jpa.open-in-view là đúng, tôi không gặp vấn đề gì với getOne nhưng sau khi đặt nó thành false, tôi đã nhận được LazyInitializationException. Sau đó, vấn đề đã được giải quyết bằng cách thay thế bằng findById.
Mặc dù có một giải pháp khác mà không thay thế phương thức getOne, và đó là đặt @Transactional tại phương thức đang gọi repository.getOne (id). Theo cách này, giao dịch sẽ tồn tại và phiên sẽ không bị đóng trong phương thức của bạn và trong khi sử dụng thực thể sẽ không có bất kỳ LazyInitializationException nào.


-2

Tôi đã có một vấn đề tương tự để hiểu tại sao JpaResposeective.getOne (id) không hoạt động và gây ra lỗi.

Tôi đã đi và đổi thành JpaResposeective.findById (id) yêu cầu bạn trả lại Tùy chọn.

Đây có lẽ là nhận xét đầu tiên của tôi về StackOverflow.


Thật không may, điều này không cung cấp và trả lời cho câu hỏi, cũng không cải thiện các câu trả lời hiện có.
JSTL

Tôi thấy, không có vấn đề.
akshaymittal143
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.