JPA создает, редактирует и удаляет объекты из базы данных

Как мне максимально упростить управление операциями создания, редактирования и удаления сущностей?

Например:

Пользователь:

public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String name;

    ...

    // Item can't exist without user
    @OneToMany(cascade=CascadeType.ALL,mappedBy = "user",orphanRemoval=true)
    private Set<Item> items = new HashSet<Item>();

    public Set<Item> getItems() { return items; }

    public void addItem(Item item) {
        items.add(item);
    }

    public void removeItem(Item item) {
        if(!items.contains(item)) return;
        items.remove(item);
    }

    // Group can exist without a user
    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH},mappedBy="users")
    private Set<Group> groups = new HashSet<Group>();

    public Set<Group> getGroups() { return groups; }

    public void setGroups(Set<Group> groups) { this.groups = groups; }

    public void addGroup(Group group) {
        groups.add(group);
    }

    publi void removeGroup(Group group) {
        if(!groups.contains(group)) return;
        groups.remove(group);
    }

    ...
}

Группа:

@Entity
public class Group {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String name;

    ...

    @ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinTable(name = "GroupToUser", joinColumns =
    @JoinColumn(name = "GroupId", referencedColumnName="Id"), inverseJoinColumns =
    @JoinColumn(name = "UserId", referencedColumnName="Id"))
    private Set<User> users = new HashSet<User>();

    public Set<User> getUsers() { return users; }

    public void setUsers(Set<User> users) { this.users = users; }

    public void addUser(User user) {
        user.addGroup(this);
    }

    publi void removeUser(User user) {
        if(!users.contains(user)) return;
        users.remove(user);
    }

    ...
}

Вещь:

@Entity
public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    private String name;

    ...

    @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    @JoinColumn(name="UserId")
    private User user;

    public Set<User> getUser() { return user; }

    public void setUser(User user) {
        this.user = user;
    }

    publi void removeUser() {
        this.user = null;
    }

    ...
}

Правильно ли я использую аннотации jpa?

Что я должен написать здесь?

EntityManager em = getEntityManager();
em.getTransaction().begin();
???
em.getTransaction().commit();

Должен ли я просто вызывать методы em.remove/persist/merge для операций удаления/создания/редактирования?

И когда я должен использовать метод javax.persistence.EntityManager.getReference в этих операциях?


person BlackCat    schedule 13.06.2016    source источник
comment
Кажется, вы понимаете, что экземпляр EntityManager используется для управления объектами JPA. Вы пробовали его тестировать?   -  person scottb    schedule 14.06.2016


Ответы (4)


Find() доставляет сущность из кеша контекста постоянства или, если его там нет, она будет загружена из базы данных.

GetReference() не загружает объект сразу. Возвращается прокси (некий объект, так называемый «заместитель» с расширенными методами загрузки фактического объекта). Итак, это реализация с помощью LazyLoading.

Только если атрибуты прокси-сервера или другие методы сохранения необходимы/вызываются, прокси-сервер взаимодействует и загружает фактический объект из базы данных.

Eg:

User us = em.find(User.class, 70992);

GetReference() используется аналогично.

 User us = em.getReference(User.class, 70922);

Если сущность для идентификатора пользователя неизвестна в контексте постоянства, выдается исключение EntityNotFoundException().

Я обычно использую метод getReference, когда мне не нужно обращаться к состоянию базы данных (я имею в виду метод получения). Просто изменить состояние (я имею в виду метод установки).

В приведенном выше случае, если я хочу обновить возраст пользователя, как показано ниже, после получения пользователя:

setAge(age);

Если я вызову метод find, провайдер JPA за кулисами вызовет

SELECT NAME, AGE FROM USER WHERE USER_ID = ?

UPDATE USER SET AGE = ? WHERE USER_ID = ?

Если я вызову метод getReference, провайдер JPA за кулисами вызовет

UPDATE PERSON SET AGE = ? WHERE USER_ID = ?

Потому что когда вы вызываете getReference, вы получаете прокси-объект.

Для остальных мы должны использовать удаление, сохранение и слияние, как вы сказали.

person Tanvi B    schedule 13.06.2016

Лично я рекомендую прочитать о шаблоне программного обеспечения репозитория и принцип единой ответственности.

Моя идея заключалась бы в том, чтобы создать, например, UserRepository, а другой класс, такой как Controller, мог бы перейти в этот репозиторий после создания необходимых объектов и попытаться сохранить их.

Должно работать так:

Сначала получите диспетчер сущностей и транзакцию, как вы это сделали, затем попытайтесь persist() сущностей. Если метод persist() обнаружит, что объект находится в постоянстве, он выдаст ошибку PersistenceException. Перехватите его, получите новую транзакцию и вызовите метод merge().

person André Silva    schedule 13.06.2016
comment
Если вы не знаете, является ли он новым, почему бы просто не использовать слияние и позволить JPA обрабатывать вставку или обновление? - person Chris; 14.06.2016

Самый простой способ - не лучший. Я думаю, что самый простой способ - это (но что касается действий по обновлению, вам лучше прочитать больше о JPQL namedQueries/orm.xml):

@WebFilter("*.htm")
public class JPAFilter implements Filter {
    private static final EntityManagerFactory entityManagerFactory
        = Persistence.createEntityManagerFactory(/* yourprojectname */);
    private static final ThreadLocal<EntityManager> entityManagers
        = new ThreadLocal<>();

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
            entityManagers.set(entityManagerFactory.createEntityManager());
        EntityManager entityManager = entityManagers.get();
        try {
            request.setCharacterEncoding("UTF-8");
            chain.doFilter(request, response);
        } finally {
            if (entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
            entityManager.close();
            entityManagers.remove();
        }
    }

    public static EntityManager getEntityManager() {
        return entityManagers.get();
    }

    @Override
    public void destroy() {
        entityManagerFactory.close();
    }
}

----

abstract class AbstractDAO {
    protected EntityManager getEntityManager() {
        return JPAFilter.getEntityManager()
    }
}

----

public class UserDAO extends AbstractDAO {
    public void create(User user) {
        getEntityManager().persist(user);
    }

    public User read(long id) {
        return getEntityManager().find(User.class, id);
    }

    public void delete(long id) {
        if (user != null) {
            getEntityManager().remove(user);
        }
    }
}

----

abstract class AbstractService {
    private EntityManager getEntityManager() {
        return JPAFilter.getEntityManager();
    }

    protected void beginTransaction() {
        getEntityManager().getTransaction().begin();
    }

    protected void commit() {
        getEntityManager().getTransaction().commit();
    }

    protected void rollback() {
        getEntityManager().getTransaction().rollback();
    }
}

----

public class UserService extends AbstractService {
    private final UserDAO userDAO = new UserDAO();

    public void create(User user) {
        beginTransaction();
        userDAO.create(user);
        commit();
    }

    public User read(long id) {
        return userDAO.read(id)
    }

    public void delete(long id) {
        userDAO.delete(id);
    }
}

----

@WebServlet("/users.htm")
public class ManageUsersServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final String VIEW = "/WEB-INF/JSP/users.jsp";
    private final transient UserService userService = new UserService();

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // here you do whatever you want using your userService methods.
    }
}

P.S. Не забывайте блокировать записи из базы данных при обновлении (используя пессимистическую блокировку или перехватывая исключение оптимистической блокировки).

Вы также можете найти много информации об этом, если будете искать "CRUD Operations with JPA". Первый учебник, который я нашел, это: http://www.objectdb.com/java/jpa/persistence/crud

person LowLevel    schedule 14.06.2016

Правильно ли я использую аннотации jpa?

На первый взгляд, я бы поставил под сомнение только отсутствие поля @Version, которое необходимо EntityManager для выполнения оптимистической блокировки, которая является стандартной защитой от потерянных обновлений, если вы не используете сериализуемый уровень изоляции (который потребует специальной настройки на многие системы баз данных).

Должен ли я просто вызывать методы em.remove/persist/merge для операций удаления/создания/редактирования?

Это самый простой и, как правило, лучший способ.

И когда я должен использовать метод javax.persistence.EntityManager.getReference в этих операциях?

getReference полезен, если вы хотите сослаться на объект, не загружая его данные из базы данных. Например, у вас может быть:

public void updateUser(Item item, int userId) {
    item.setUser(entityManager.getReference(User.class, userId)); // does not require a query
    entityManager.merge(item);
}

Транзакции

Вы захотите откатить транзакцию, если возникнет исключение, и закрыть entityManager, как только вы закончите с ним (как в случае успеха, так и в случае неудачи).

В связи с этим, если ваше приложение не крошечное, вам следует изучить декларативное управление транзакциями, чтобы вам не приходилось кодировать их для каждой операции. Например, в Spring вы просто аннотируете класс @Transactional, чтобы Spring запускал (и заканчивал) транзакцию всякий раз, когда вызывается метод.

person meriton    schedule 14.06.2016