Страницы

Поиск по вопросам

Показаны сообщения с ярлыком jpa. Показать все сообщения
Показаны сообщения с ярлыком jpa. Показать все сообщения

среда, 15 апреля 2020 г.

Работа с БД при помощи JPA 2

#jpa #java

                    
Здравствуйте. Помогите, пожалуйста, разобраться, JPA 2.
Вопрос вот в чем: 

Допустим, у меня есть некоторый класс, работающий с БД. Разные объекты его создают,
используют и т.д. Чтобы не было много "мертвых" подключений, их постоянно надо закрывать.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("commonStorage");
EntityManager em = emf.createEntityManager();
// операции
em.close();
emf.close();

Но создание нового EntityManager занимает время. Может, есть что-то, что я упустил?
Может, надо использовать один EntityManagerFactory на все приложение? Или другие варианты.    


Ответы

Ответ 1



Да, EntityManagerFactory тяжеловесный объект и не стоит его создавать при каждом запросе. Лучше использовать стратегию - один EntityManager на один запрос.

четверг, 27 февраля 2020 г.

Как обернуть вызов сервисов из рест-контроллера для отлова исключений?

#java #spring #jpa


Есть рест - сервис на spring:

@RestController
public class UserController {
    @Autowired
    private UserService service;

    @RequestMapping(value = "/users", method = RequestMethod.POST)
    public Users add(@RequestBody Users users,
                              @RequestParam(value = "id", required = false) String id) {
        return service.add(id, users.getUsers());
    }

    @RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
    public @ResponseBody Users findById(@PathVariable(value = "id") String id) throws
Exception {
        return service.find(id);
    }
}


Который обменивается с клиентом с использованием xml и jaxb. При этом сущность Users
выглядит следующим образом:

@XmlRootElement(name = "Users")
@NoArgsConstructor
@AllArgsConstructor
@XmlAccessorType(XmlAccessType.FIELD)
@Getter
@Setter
public class Users {
    @XmlElement(name = "users")
    private List Users;

    @XmlElement(name="UserError")
    private UserError error;

    public void appendUsers(Users Users){
        if (Users == null) return;

        if (Users.getError() != null){
            this.error = Users.getError();
        }

        if ( Users.getUsers() != null) {
            if (this.Users == null){
                this.Users = new ArrayList<>();
            }
            this.Users.addAll(Users.getUsers());
        }
    }
}


То есть, по сути, это два поля, в одном из котором хранится список пользователей,
в другом - ошибка если возникла.

В процессе выполнения service.add может возникнуть ошибка, например база данных оказалась
недоступна, и метод вернет exception.
Решением в лоб является обертка всех вызовов сервисов в try catch и, если возникло
исключение, запись этой ошибки в Users.error.

Есть ли более элегантное решение?
    


Ответы

Ответ 1



Конечно есть. Для того чтобы отловить ошибку, допустим, UserNotFoundException в UserController необходимо добавить метод с аннотацией @ExceptionHandler: @ExceptionHadler(UserNotFoundException.class) @ResponseStatus(HttpStatus.NOT_FOUND) @ResponseBody private String handleUserNotFoundException( UserNotFoundException ex) { return "user not found"; } Для того чтобы отловить ошибку, допустим, IllegalArgumentException изо всех RestController-ов необходимо обьявить класс с аннотацией @ControllerAdvice: @ControllerAdvice public class RestExceptionHandler { @ExceptionHandler(IllegalArgumentException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody public String handleIllegalArgumentException(...) {...} }

четверг, 23 января 2020 г.

Как работать с формами которые содержат большое количество данных

#java #sql #jpa #шаблоны_проектирования


Есть ли какой то патерн для работы с формами которые содержат большое количество
данных которые не реально полностью выгрузить и базы показать пользователю на UI и
отравить во внешнюю систему. 

Задача:
Есть объект который по мимо прочего содержит таблицу с данными. Данных в таблице
на столько много что их нельзя все поднять.


Нужно чтобы пользователь мог открыть этот объект для редактирования. Таблица при
этом поддерживает пэйджинг.
Нужно, чтобы пользователь мог редактировать данные в таблице.
Нужно, чтобы пользователь мог сохранить объект после редактирования или отменить
и не сохранять свои изменения
При этом нужна скорость отклика на операцию сохранения результата приемлимая для UI.


Вопрос очень общий. Меня интересует подход. В частности как такое реализовать с реляционной
базой данных, а ещё точнее с JPA 2.1

Что я уже пробовал:


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


Поделитесь своими знаниями и опытом по такой проблеме. Может быть есть паттерн или
какое нибудь общепринятое решение?
    


Ответы

Ответ 1



Описанная вами проблема решается "паттерном" Диалоги (Conversations). Пример “долгоиграющего” диалога: Открывается первый экран диалога. Данные, показываемые пользователю, подгружаются в отдельной сессии Session и БД-транзакции. Пользователь может модифицировать любые поля диалога. После пяти минут редактирования, пользователь использует UI элемент для сохранения. Изменения отразились в БД. Пользователь также ожидает эксклюзивного доступа к данным на время сессии редактирования. Хотя в описанном примере есть несколько случаев доступа к БД, с точки зрения пользователя, данная серия шагов представляет одну единицу совершенной работы (Unit of Work). Есть множество путей реализации этого в приложении. Первый (наивный) метод заключается в удержании открытыми сессий Session и транзакции на время редактирования пользователя, с использованием механизмов синхронизации БД для обеспечения эксклюзивного доступа пользователя к редактируемым данным, и предотвращению обращения к ним со стороны других пользователей, гарантируя изоляцию и атомарность. Это анти-паттерн, так как лишняя синхронизация является узким местом при проблемах производительности, встающих в высоконагруженных приложениях. Другой метод - использование ряда транзакций БД для реализации диалога с БД. В данном случае, обеспечение изоляции бизнес-процессов ложится на плечи приложения. Один диалог обычно покрывает несколько транзакций. Множественные доступы к БД могут быть атомарными, если только одна транзакция (обычно последняя) осуществляет запись в БД. Все другие только читают данные. Типичный путь реализации – через создание wizard-style диалога, покрывающего несколько шагов цикла запрос/ответ. Hibernate включает в себя некоторые возможности, позволяющие реализовать подобный функционал: Автоматическое версионирование: Hibernate может осуществлять за вас concurrency-контроль. Он может автоматически обнаружить, осуществлялись ли сторонние обновления данных за время ожидания пользователя. Отсоединенные (Detached) объекты: если вы предпочтете использовать шаблон сессия-на-запрос, все загруженные экземпляры будут отсоединены за время ожидания пользователя. Hibernate позволяет вам обратно подсоединить объекты и сохранить модификации. Данный паттерн называется сессия-на-запрос-с-отсоединенными объектами. Автоматическое версионирование используется для изоляции параллельно выполняющихся запросов. Расширенная сессия: сессия Session может быть отсоединена от нижележащего JDBC соединения после того, как БД транзакция будет закоммичена, и переподсоединена, когда возникнет новый клиентский запрос. Этот паттерн называется сессия-на-диалог, делающий повторное соединение (reattachment) объектов ненужным. Автоматическое версионирование используется для изоляции параллельных модификаций, при этом сессия не может быть сброшена (flushed) автоматически, только явно. Т. е. для реализации диалогов в Hibernate можно использовать паттерны Сессия-на-запрос-с-отсоединенным-объектами и сессия-на-диалог. Для описания паттерна Диалог я использую Hibernate, т. к. в нём точно есть встроенная поддержка паттера и он описан в официальной документации. Про поддержку паттерна в JPA мне говорить сложно, но быстрое и поверхностное гугление показывает, что в JPA паттерн реализовать можно. Источники: Документация разработчика Hibernate – Глава II. Транзакции и контроль многопоточности Hibernate ORM User Guide Implementing conversations В последнем источнике есть примеры различных реализаций паттерна Диалог.

пятница, 10 января 2020 г.

Использование criteria из JPA

#java #hibernate #jpa


В новом Hibernate использование Criteria является Deprecated. Как я могу заменить
такой код с использованием org.hibernate.Criteria:

Criteria criteria = session.createCriteria(Role.class);
long id = ((Role) criteria.add(Restrictions.eq("name", name)).uniqueResult()).getId();


на его эквивалент с использованием javax.persistence.criteria.CriteriaBuilder из JPA ?
    


Ответы

Ответ 1



em - экземпляр EntityManager. public Long query(String name) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Role.class); Root root = cq.from(Role.class); cq.where(cb.equal(root.get("name"), cb.parameter(String.class, "name"))); // cq.distinct(true); Query q = em.createQuery(cq); q.setParameter("name", name); List result = q.getResultList(); if (result.size() > 0) { return result.get(0).getId(); } return null; }

Ответ 2



Ну вот когда есть что скопипастить (спасибо, Виктор) public Long query(String name) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Role.class); Root root = cq.from(Role.class); cq.where(cb.equal(root.get("name"), cb.parameter(String.class, "name"))); try { // Тут возможно надо делать приведение типов, // либо вызывать вариант метода, в котором указывается тип возвращаемого параметра // em.createQuery(cq, Role.class)... // либо и так сойдёт return em.createQuery(cq).setParameter("name", name).getSingleResult().getId(); } catch (NoResultException ex) { return null; } } или public Long query(String name) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Role.class); Root root = cq.from(Role.class); cq.where(cb.equal(root.get("name"), cb.parameter(String.class, "name"))); List roles = em.createQuery(cq).setParameter("name", name).getResultList(); return roles.isEmpty() ? null : roles.get(0).getId(); } Ну то есть вариант Виктора без лишнего distinct и местами собранный в одну строку за счёт fluent-а, широко применяемого в JPA API. Если используете генерацию статичной метамодели, тогда root.get("name") заменяется на root.get(Role_.name). Типа безопасно. Чо. Иначе на фига Вам вообще criteria, когда есть текстовый JPQL, а ещё лучше SQL?

понедельник, 6 января 2020 г.

JpaRepository + Hibernate + OneToMany вылетает LazyInitializationException

#java #spring #hibernate #jpa #spring_data


реализую отношение one-to-many по этому туториалу а также этому спринговому туториалу.
Я НЕ использую сессии и прочий hibernate напрямую, я использую JpaRepository и аннотации
JPA. Есть объекты Owner (one) & Book (many):

первая таблица:

@Entity
@Table(name = "owners")
public class Owner implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "owner_id", nullable = false, unique = true)
    private Long id;

    @Column(name = "owner_name", nullable = false)
    private String name;

    @OneToMany(fetch = FetchType.LAZY,mappedBy = "owner")
    private Set books= new HashSet<>(0);

    public Worker() {
    }
}


вторая таблица: 

@Entity
@Table(name = "books")
public class Book implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "book_id", unique = true, nullable = false)
    private Long id;

    @Column(name = "book_name", nullable = false, unique = true)
    private String name;


    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "owner_id")
    private Owner owner;

    public Task() {
    }
}


слой репозиториев стандартный, без реализации:

public  interface OwnerRepository extends JpaRepository {}

public  interface BookRepository extends JpaRepository {}


Owner service: (book service такой же)

@Service
@Transactional
public class OwnerServiceImpl implements OwnerService {

    @Autowired
    private OwnerRepository ownerRepository;

    @Override
    public Owner create(Owner owner) {return ownerRepository.save(owner);}

    @Override
    public Owner read(Long id) {return ownerRepository.findOne(id);}

    @Override
    public List readAll() {return ownerRepository.findAll();}

    @Override
    public void delete(Owner owner) {ownerRepository.delete(owner);}

}


Создав два класса service, я захотел протестить их. Создал несколько Owner,Book ну
и пытаюсь связать их как указано здесь, т.е. добавляю в объектe Owner в Set новую книжку,
а в объект Book обновляю ссылку на Owner. Сохраняю в репозиторий сначала Owner, затем
Book. Затем пытаюсь прочитать Owner из репозитория, а во время обращения к полю выдаёт 


  unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException'
exception.


может я вообще неправильно работаю с репозиторием и JPA? как нужно сохранять/обновлять
ссылки в сущностях?

===UPDATE===

мне кажется я решил проблему, использовав @NamedEntityGraph & @EntityGraph.
Я аннотировал Owner 

@NamedEntityGraph(name = "Owner.books",
    attributeNodes = @NamedAttributeNode("books"))


Но в интерфейсе репозитория пришлось переопределить стандартные методы:

@Override
@EntityGraph(value = "Owner.books", type = EntityGraph.EntityGraphType.LOAD)
List findAll();


@Override
@EntityGraph(value = "Owner.books", type = EntityGraph.EntityGraphType.LOAD)
OwnerfindOne(Long aLong);


Тоже самое сделал с Book. Когда я делаю запрос из репозитория Owner на считывание
объекта, всё загружается нормально - ссылки в Set ссылаются на объекты Book. 
Но когда я делаю запрос к Book репозиторию - объект Book имеет ссылку на Owner, НО
в этом внутреннем Owner ссылки на дополнительные книги бросают LazyInitException. Почему?
ссылки просматриваю в Idea на брек поинте.
    


Ответы

Ответ 1



У вас стоит fetch = FetchType.LAZY это значит, что хибернейт не будет инициализировать эти поля пока вы к ним не обратитесь. Но т.к. вы обращаетесь к этим полям за пределами транзакционных методов, он не может это сделать и выкидывает ошибку. Чтобы этого избежать надо, что метод, который обращается к этим полям был с аннотацей Transactional. В вашем случае это EntitiesServicesTest.ooops(). Либо можно немного изменить реализацию OwnerServiceImpl.read(). Сделать такое: @Override public Owner read(Long id) { Owner owner = ownerRepository.findOne(id); owner.getBooks().iterator(); return owner; } Или как предложили в комментариях: Hibernate.initialize(owner.getBooks()); Это хак, но он заставит хибернейт инициировать коллекцию. НО! Возможно это не всегда надо и тогда надо выбрать первый вариант и отталкиваться от здравого смысла, смотреть, где надо навешивать аннотацию, а где нет.

Ответ 2



Стоит также упомянуть, что fetch type по дефолту как раз LAZY. Я для решения проблемы поставил fetch = FetchType.EAGER и всё заработало.

среда, 25 декабря 2019 г.

Сохранение цен в логах

#java #hibernate #jpa #интернет_магазин #ecommerce


Есть таблица товаров (Goods). У каждого товара есть цена. При создании заказа (Order)
к нему прикрепляются товары с помощью дополнительной таблицы Goods__Order. При этом
после заказа цены товаров в заказе не должны меняться, даже если они изменились в таблице
Goods.

Один из вариантов - сохранять цену в таблицу Goods__Order. Но количество полей, которые
не должны меняться, возможно, станет больше, и поэтому не хочется их все переносить
в эту таблицу + вариант не очень универсальный.

Второй вариант, как мне кажется, можно логировать изменения в таблице Goods в таблицу
Goods_audit, которая будет содержать изменения полей + номер версии (revision number).
Это доступно через Hibernate Envers. Но тогда в таблицу Goods_Order нужно будет вносить
id товара + номер версии + я не нашел примеров, что так делают.

Какой вариант будет лучше? Учитывая, огромное количество интернет-магазинов, наверное
уже есть более-менее известное решение для этого. Да и во вариантах на php никакого
Hibernate Envers нет.
    


Ответы

Ответ 1



Заказ не является объектом бухгалтерского учета, это объект процессов логистики и взаимоотношения с клиентами. Однозначных рекомендаций по логистике и CRM не существует, правильная постановка таких процессов является ноухау организации. Пока заказ не оплачен, он является по сути предложением клиенту и объектом любой переоценки. В магазине могут действовать правила расчета цены отдельного товара для клиента с учетом скидок (иногда и надбавок) - скидка по промокоду, скидка за объем заказа (суммарный по всем товарным позициям заказа), персональная скидка клиента (например - за историю заказов, либо скидка отдельным категориям клиентов), и т.д. За время, когда заказ ожидает оплаты, может стартовать кампания по стимулированию спроса - на отдельные товары/группы начинает действовать значительная скидка, которая отменяет другие скидки (промокод, песональные и т.д.). После того, как заказ оплачен (тем более - если исполнен), все текущие условия - базовая цена, скидки, условия доставки и пр. - должны быть зафиксированы и не подлежать дальнейшим изменениям. В развитых eComm платформах типа Magento текущие цены ведутся в отдельных объектах - прайс-листах, могут настраиваться правила перерасчета цен (скажем, в зависимости от валютных котировок для импортных товаров), ведется история изменения прайс-листов. Правила перерасчета неоплаченных заказов также могут настраиваться. Так должно быть, а реализуется ли это в конкретной eComm и как - зависит от платформы. Если сами пишете платформу - посоветуйтесь с отделом продаж :-)

Ответ 2



Топорный вариант В таблицу Order(заказ)пишите цену на момент заказа добавить поле price(цена). Создать таблицу price_history[ид товара,номер ревизии,цена, дата и прочее если надо] и вносить каждое изменения цены туда. В таблице Goods хранится всего лишь ид ревизии цены.

javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread

#java #spring #hibernate #spring_mvc #jpa


GenericDao.java:

@Repository
public abstract class GenericDao implements GeneralDao {

    private Class className;

    public GenericDao(Class className) {
        this.className = className;
    }

    @PersistenceContext
    private EntityManager entityManager;

    public EntityManager getEntityManager() {
        return entityManager;
    }

    @Override
    public void add(T object) {
        try {
            getEntityManager().persist(object);
        } catch (HibernateException e) {
            throw new DaoException(ErrorMessage.ADD_ENTITY_FAIL, e);
        }
    }

    @Override
    public void update(T object) {
        try {
            getEntityManager().merge(object);
        } catch (HibernateException e) {
            throw new DaoException(ErrorMessage.UPDATE_ENTITY_FAIL, e);
        }
    }

    @Override
    public void remove(T object) {
        try {
            getEntityManager().remove(object);
        } catch (HibernateException e) {
            throw new DaoException(ErrorMessage.REMOVE_ENTITY_FAIL, e);
        }
    }

    @Override
    public T getById(int id) {
        try {
            return getEntityManager().find(this.className, id);
        } catch (HibernateException e) {
            throw new DaoException(ErrorMessage.GET_BY_ID_ENTITY_FAIL, e);
        }
    }

    public abstract List getAll() throws DaoException;

}


GenericService.java

@Service
public abstract class GenericService implements GeneralService {
    private static Logger logger = Logger.getLogger(GenericService.class);

    @Autowired
    private GenericDao dao;

    @Transactional
    @Override
    public void add(T object) throws ServiceException {
        try {
           dao.add(object);
        } catch (DaoException e) {
            logger.debug(e);
            throw new ServiceException(e.getMessage());
        }
    }

    @Transactional
    @Override
    public void update(T object) throws ServiceException {
        try {
            dao.update(object);
        } catch (DaoException e) {
            logger.debug(e);
            throw new ServiceException(e.getMessage());
        }
    }

    @Transactional
    @Override
    public void remove(T object) throws ServiceException {
        try {
            dao.remove(object);
        } catch (DaoException e) {
            logger.debug(e);
            throw new ServiceException(e.getMessage());
        }
    }

    @Transactional(readOnly = true)
    @Override
    public T getById(int id) throws ServiceException {
        try {
            return dao.getById(id);
        } catch (DaoException e) {
            logger.debug(e);
            throw new ServiceException(e.getMessage());
        }
    }

    @Transactional(readOnly = true)
    @Override
    public List getAll() throws ServiceException {
        try {
            return dao.getAll();
        } catch (DaoException e) {
            logger.debug(e);
            throw new ServiceException(e.getMessage());
        }
    }

}


UserServiceImpl.java:

@Service
public class UserServiceImpl extends GenericService implements UserService {
    private static Logger logger = Logger.getLogger(UserServiceImpl.class);

    @Autowired
    private UserDao userDao;

    @Transactional
    @Override
    public String checkUser(String userLogin, String userPassword) throws ServiceException {
        String namePage = "errorAuthorization";
        List userList;
        try {
           userList = userDao.getByLoginAndPassword(userLogin, userPassword);
        }  catch (DaoException e) {
            logger.debug(e);
            throw new ServiceException(e.getMessage());
        }
        if(userList.size() != 0) {
            return UserRoleChecker.defineUserPage(userList.get(0));
        }
        return namePage;
    }

    @Transactional
    @Override
    public void addUser(String userLogin, String userPassword, String userMail) throws
ServiceException {
        Role role = new Role(0L, RoleType.USER);
        User user = new User(0L, userLogin, userPassword, userMail, role);
        add(user);
    }

}


UserController.java:

@Controller
public class UserController {
    private static String className = UserController.class.getName();
    private static Logger logger = Logger.getLogger(UserController.class.getName());

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/check_user", method = RequestMethod.POST)
    public ModelAndView authorizationUser(HttpServletRequest request, HttpServletResponse
response) {
        ModelAndView modelAndView = new ModelAndView();
        String returnPage;
        try {
            returnPage = userService.checkUser(request.getParameter(RequestParameter.USER_LOGIN),
request.getParameter(RequestParameter.USER_PASSWORD));
        } catch (ServiceException e) {
            logger.debug(e);
            returnPage = ErrorHandler.returnErrorPage(e.getMessage(), className);
        }
        modelAndView.setViewName(returnPage);
        return modelAndView;
    }

    @RequestMapping(value = "/add_user", method = RequestMethod.POST)
    public ModelAndView registrationUser(HttpServletRequest request, HttpServletResponse
response) {
        ModelAndView modelAndView = new ModelAndView();
        String returnPage = Page.SUCCESSFUL_REGISTRATION;
        try {
            userService.addUser(request.getParameter(RequestParameter.USER_LOGIN),
request.getParameter(RequestParameter.USER_PASSWORD), request.getParameter(RequestParameter.USER_MAIL));
        }  catch (ServiceException e) {
            logger.debug(e);
           returnPage = ErrorHandler.returnErrorPage(e.getMessage(), className);
        }
        modelAndView.setViewName(returnPage);
        return modelAndView;
    }

}


root-context.xml:




    

    
    
    

    
        
        
        
        
        
        
    

    
        
        
        
            
                
                
                
            
        
        
            
                org.hibernate.dialect.MySQLDialect
                true
                true
                2
                true
                update
            
        
        
    

    

    

    

    
        
        
        
    




logs:

org.springframework.web.servlet.FrameworkServlet 2017-05-10 22:23:59,107 DEBUG -
Could not complete request
javax.persistence.TransactionRequiredException: No EntityManager with actual transaction
available for current thread - cannot reliably process 'persist' call
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:282)
    at com.sun.proxy.$Proxy27.persist(Unknown Source)
    at by.netcracker.artemyev.dao.GenericDao.add(GenericDao.java:35)
    at by.netcracker.artemyev.service.GenericService.add(GenericService.java:24)
    at by.netcracker.artemyev.service.impl.UserServiceImpl.addUser(UserServiceImpl.java:48)
    at by.netcracker.artemyev.web.UserController.registrationUser(UserController.java:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:475)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:495)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:767)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1354)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)


Почему возникает эта ошибка и как её исправить?
    


Ответы

Ответ 1



Проблема в том, что spring не строит прокси-объект поверх UserServiceImpl, а внедряет его напрямую в UserController, что видно из твоего исключения. Причины, я полагаю, в том, что твой root-context.xml относится к контексту всего приложения, т.е. инициализируется через ContextLoaderListener. Внутри указанного контекста UserServiceImpl имеет транзакционность. Однако, у тебя также имеется некая конфигурация (тут лучше бы приложить web.xml) относящаяся к DispatcherServlet, в которой инициализируется UserController и как раз нет tx:annotation-driven. Соответственно в UserController попадет чистый бин, а не его прокси. Быстро решить данную проблему можно убрав @Service с самого класса UserServiceImpl и определив его в root-context.xml

Ответ 2



У меня была подобная проблема. Исправил так - в контексте для сервлета в аннотации component-scan указал не корневое пространство имен, а конкретно то, которое хранит Rest-контроллер src/main/webapp/spring/appServlet-context.xml После данных изменений рутовый контекс рулил бинами сервисов и проблем не было.

вторник, 24 декабря 2019 г.

Примеры записи аннотаций @OneToMany @OneToOne @ManyToMany @ManyToOne [Hibernate]

#hibernate #jpa #аннотации


Интересуют как создавать сущности с аннотациями:  


@OneToOne  
@OneToMany   
@ManyToOne  
@ManyToMany


Так же вариации с bidirectional и unidirectional

Так же было бы неплохо добавить объяснений типо Cascade.**
    


Ответы

Ответ 1



Данный ответ будет модифицироваться по мере получения нужных знаний Update 11.09.2019: Вместо List<> используйте где возможно Set<> ...иначе при работе с join fetch (кастомном hql-запросе) у вас будет выпадать ошибка: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple Bags: Более подробно об ошибке написано тут Не используйте CascadeType.ALL Если кратко - лучше использовать только с @OneToOne, т.к. при использовании со множественными связями могут удалиться ненужные записи из бд. Данный момент подробно описан в данном источнике Все ниже приведенные примеры я подробно показываю в своем мини-проекте, который можно скачать с GitHub О каскадировании можно почитать тут Project Lombok В моих ответах я использую аннотации Project Lombok: - @Data - Аннотация, которая добавляет в ваш проект Getters/Setters, Equals, ToString, HashCode - @AllArgsConstructor - Конструктор, содержащий все глобальные переменные, записанные в данном классе - @NoArgsConstructor - Пустой конструктор. Если мы не хотим в самом конструкторе прописывать данные, а использовать Setter - @ToString(exclude - "НазваниеПеременной") - При работе с bidirectional у нас будет зацикливаться объекты. Чтобы этого не допустить - надо от них избавиться Другие аннотации Project Lombok Установка плагина Project Lombok в IntelliJ и Eclipse @OneToOne Вариант unidirectional: От Пользователя к Покупателю, но не от Покупателя к Пользователю User.java: @Data @AllArgsConstructor @NoArgsConstructor @Entity @Table(name = "users") public class User { @Id @GeneratedValue private long id; @Column private String username; //Some code @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "customer_id", unique = true) private Customer customer; } Customer.java @Data @Builder @AllArgsConstructor @NoArgsConstructor @Entity @Table(name = "customer") public class Customer { @Id @GeneratedValue @Column(name = "country_id") private long id; //Some code @Column(name = "customer_name") private String customerName; } Вариант bidirectional: От Пользователя к Покупателю и от Покупателя к Пользователю @OneToMany Вариант unidirectional: У Поста есть несколько Комментариев, но нам не нужно от Комментария искать Пост Post.java: @Data @AllArgsConstructor @NoArgsConstructor @Entity @Table(name = "post") public class Post { @Id @GeneratedValue @Column(name = "post_id") private Long id; @Column private String postHeader; @OneToMany( cascade = CascadeType.ALL, orphanRemoval = true ) private List comments = new ArrayList<>(); public void addComment(Comment comment) { comments.add(comment); } public void removeComment(Comment comment) { comments.remove(comment); } } Comment.java: @Data @AllArgsConstructor @NoArgsConstructor @Entity @Table(name = "comment") public class Comment { @Id @GeneratedValue @Column(name = "postcom_id") private Long id; @Column private String text; } Вариант bidirectional: *Профессор на курсе может узнавать информацию о студентах, в тоже самое время студенты могут узнавать информацию о Профессоре * Professor.java: @Data @NoArgsConstructor @AllArgsConstructor @Entity @ToString(exclude = "students") @Table(name = "professor") public class Professor { @Id @GeneratedValue @Column(name = "professor_id") private Long id; @Column private String name; @OneToMany( mappedBy = "professor", cascade = CascadeType.ALL, orphanRemoval = true ) List students = new ArrayList<>(); /* As you see we need to do something like "recursion" below */ public void addStudent(Student student) { students.add(student); student.setProfessor(this); } public void removeStudent(Student student) { students.remove(student); student.setProfessor(null); } } Student.java @Data @NoArgsConstructor @AllArgsConstructor @Entity @ToString(exclude = "professor") @Table(name = "student") public class Student { @Id @GeneratedValue @Column(name = "student_id") private Long id; @Column private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="professor_id") private Professor professor; } @ManyToOne @ManyToMany Вариант unidirectional: Пользователь может иметь несколько Ролей, Роль могут быть присвоина нескольким Пользователям User.java @Entity public class User { @Id @GeneratedValue @Column(name = "user_id") private long id; ... @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) private List roles = new ArrayList<>(); public void addRoles(Role role) { roles.add(role); } public void removeRoles(Role role) { roles.remove(role); } } Role.java @Entity public class Role { @Id @GeneratedValue @Column(name = "role_id") private int id; @Column(name = "role") private String role; } Вариант bidirectional: Трейдер может торговать на нескольких Биржах, Биржы могут посещаться несколькими Трейдерами Trader.java: @Data @AllArgsConstructor @NoArgsConstructor @Entity @ToString(exclude = "stockmarkets") @Table(name = "trader") public class Trader { @Id @GeneratedValue @Column(name = "trader_id") private Long id; @Column(name = "trader_name") private String traderName; @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE }) @JoinTable(name = "TRADER_STOCKMARKET", joinColumns = { @JoinColumn(name = "trader_id") }, inverseJoinColumns = { @JoinColumn(name = "stockmarket_id") }) private List stockmarkets = new ArrayList<>(); /* We need to add methods below to make everything work correctly */ public void addStockmarket(Stockmarket stockmarket) { stockmarkets.add(stockmarket); stockmarket.getTraders().add(this); } public void removeStockmarket(Stockmarket stockmarket) { stockmarkets.remove(stockmarket); stockmarket.getTraders().remove(this); } } Stockmarket.java @Data @AllArgsConstructor @NoArgsConstructor @Entity @ToString(exclude = "traders") @Table(name = "stockmarket") public class Stockmarket{ @Id @GeneratedValue @Column(name = "stockmarket_id") private Long id; @Column(name = "stockmarket_name") private String stockmarketName; @ManyToMany(mappedBy="stockmarkets") private List traders = new ArrayList<>(); /* We need to add methods below to make everything work correctly */ public void addTrader(Trader trader) { traders.add(trader); trader.getStockmarkets().add(this); } public void removeTrader(Trader trader) { traders.remove(trader); trader.getStockmarkets().remove(this); } }

среда, 18 декабря 2019 г.

Update сущность с полем updatable = false

#java #hibernate #orm #jpa #spring_data


Использую JPA + Spring Data + Hibernate для записи данных. Есть сервис с вот таким
методом (реализация JPA репозитория стандартная):

@Transactional
public EmployeeJpaBean create(final EmployeeJpaBean employeeJpaBean) {
    return employeeJpaRepository.saveAndFlush(employeeJpaBean);
}


Сущность EmployeeJpaBean имеет такое вот поле:

@Column(name = "empl_name", unique = true, nullable = false, updatable = false)
private String name;


Как видно - оно не должно быть updatable.
Проверяю это в юнит тесте вот так:

@Test
public void updateNameTest() {
    EmployeeJpaBean employeeJpaBean = new EmployeeJpaBean();
    employeeJpaBean.setName("old");
    EmployeeJpaBean saved = employeeService.create(employeeJpaBean); //1
    Long id = saved.getId();

    saved.setName("new name");
    saved = employeeService.create(employeeJpaBean); //2
    //падает Assert.assertEquals("old", saved.getName());
    EmployeeJpaBean read = employeeService.read(id); //3
    Assert.assertEquals("old", read.getName());
}


На закомментированной строке Assertion падает. Глянул что пишет Hibernate - при выполнении
строк кода помеченными 2 и 3 - выполняется один и тот же select, но результат на выходе
разный.


Во первых, почему разный результат select'ов?
а во вторых - как сделать так, чтобы неверные данные не возвращались из метода saveAndFlush
? (ведь поле не updatable).

    


Ответы

Ответ 1



В документации сказано, что updatable=false влияет только на генерацию UPDATE запросов. В строке (1) мы создаем новую сущность и у нас должен генерироваться SQL INSERT запрос. В строке (2) мы вызываем метод employeeJpaRepository.saveAndFlush, который по документации (исходный код) должен вызвать save(entity) and flush(). @Transactional public S saveAndFlush(S entity) { S result = save(entity); flush(); return result; } В свою очередь метод save(entity) имеет такой вид: @Transactional public S save(S entity) { if (entityInformation.isNew(entity)) { em.persist(entity); return entity; } else { return em.merge(entity); } } Т.к. объект у нас уже существует, то будет вызван метод em.merge(entity); Идем в описание метода merge. Обращаемся к JSR-220 (вот ссылка на pdf, но возможно, чтобы открыть надо принять лицензионное соглашение). Идем в секцию: 3.2.4.1 Merging Detached Entity State The semantics of the merge operation applied to an entity X are as follows: ... ... If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been anno- tated with the cascade element value cascade=MERGE or cascade=ALL annotation. Более простой вариант из интернета: if the entity is already in the persistence context (session), no action is taken, except for cascades Вольный перевод: Если Х это managed сущность, то она будет проигнорирована при merge операции, но она будет применена к каскадно связанным сущностям... Но почему же у нас все-таки идет SELECT в строке (2)? Идет запрос за "самой последней" версией нашей сущности, чтобы потом механизмом dirty checking мы нашли изменения и смогли сгенерировать UPDATE query. Подытоживая все вышесказанное и отвечая на вопросы: Результат обоих запросов SELECT одинаковый, но просто при merge возвращается уже существующий объект из сессии, в котором уже изменено значение name. Сходу могу предложить модифицировать метод таким образом @Transactional public EmployeeJpaBean create(final EmployeeJpaBean employeeJpaBean) { employeeJpaRepository.saveAndFlush(employeeJpaBean); return employeeService.read(employeeJpaBean.getId()); } Но над вторым ответом на вопрос я еще подумаю...

понедельник, 9 декабря 2019 г.

Стоит ли делать составной первичный ключ?

#jpa #база_данных #orm


Изучаю вот этот вопрос, там такая фраза в ответе


  Composite primary keys typically arise
  when mapping from legacy databases
  when the database key is comprised of
  several columns.


Я правильно понимаю, что legacy это практически синоним слову "устаревший"?
Если так, то стоит ли, при разработке нового приложения и новой базы данных, делать
составные ключи? Или просто добавить ещё один столбец, который по сути ничего не будет
делать, ведь всю работу с бд обычно выполняют фреймворки?
    


Ответы

Ответ 1



Это довольно холиварный вопрос, и единственно верного ответа тут нет. Но чаще всего мнения сходятся к тому, что ключи должны быть не составными, и в них не должно храниться какой-либо информации о сущности. То есть это должно быть какое-то (обычно целочисленное автоматически инкрементируемое) поле, обеспечивающее уникальность записи

Ответ 2



Существует несколько устаревший подход в проектировании БД, иногда ошибочно называемый "академическим", когда первичный ключ отношения выбирается как один из естественных ключей отношения. Обычно таким ключом становится имя записи. В приведенном вами вопросе именно такой подход и используется. Но этот подход устарел по многим причинам. Первая: первичный ключ записи - это то, что используется для ссылок на запись как в самой БД, так и за ее пределами. Желательно делать его как можно меньше - все же большинство естественных ключей строковые. Причина вторая - многие вещи, выглядящие на первый взгляд как естественные ключи, на самом деле не являются такими. К примеру, среди людей бывают полные тезки (да еще и родившиеся в один день). В магазине может быть два товара с одним и тем же названием в разных отделах... Причина третья - естественный ключ еще и может изменяться, что так же ведет к проблемам. Так, для человека естественным ключом мог бы быть номер паспорта или свидетельства о рождении - но эти документы могут быть заменены при достижении определенного возраста или в случае утери. Поэтому хорошей практикой является введение суррогатного ключа - это новое поле, не имеющее никакого отношения к предметной области. Обычно его тип - автоинкрементное 32х-разрядное целое, реже - 64х-разрядное. Еще это может быть UUID. Как правило, при наличии такого поля нет никакого смысла в составных ключах. Тем не менее, иногда составные ключи бывают полезны. Вот эти случаи: Таблицы-связки для отношений "много ко многим". Обычно такие таблицы создаются и управляются библиотеками ORM самостоятельно - но иногда приходится создавать их отдельно. В таких таблицах естественным ключом является вся запись целиком - и нет никаких причин создавать отдельный суррогатный ключ. Некоторые дочерние подобъекты, не имеющие смысла в отрыве от родительского. Если мы делаем систему для тестирования учащихся - то иногда имеет смысл обращаться к ответу на вопрос как к 5му ответу на 147й вопрос - а не к 3423му ответу вообще. А иногда наоборот. Использование отношений в БД для дополнительной проверки целостности. Существует так называемая доменно-ключевая нормальная форма, в которой любые ограничения на данные реализуются в формате внешних ключей. В таком случае иногда имеет смысл включать дополнительные поля в первичный ключ.

Ответ 3



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

пятница, 29 ноября 2019 г.

Уровень изоляции транзакций и Hibernate

#java #sql #hibernate #orm #jpa


Первый вопрос. Какая связь существует между уровнем изоляции транзакций, параметрами
JPA Lock.READ / Lock.WRITE и версиями @Version (Оптимистическая блокировка)? Я более
менее понял по отдельности каждую из этих 3 разделов, но не понимаю, как они связаны
между собой.  Выбранный уровень изоляции транзакций дает некие «гарантии» при проведении
операций с таблицами.  Параметры Lock.READ/Lock.WRITE позволяют добиться блокировки
полей таблицы при проведении операций. Если я правильно понял, для этого как раз и
используются поле @Version.  Но все таки не до конца улавливаю связь между эти тремя
понятиями. Или они дополняют друг друга, или это разные способы решения одних и тех
же проблем. В общем, буду очень благодарен за ответ или же ссылку, где можно прочесть
информацию именно по вопросы связи между этими понятиями и их совместном использовании.
Второй вопрос. Каким способом в Hibernate можно указать требуемый уровень изоляции
транзакции для конкретной транзакции? И принято ли так делать?
Спасибо.    


Ответы

Ответ 1



Попробую ответить по порядку: Как соотносятся Lock и уровни изоляции Уровень изоляции транзакций (transaction isolation level) - относится к чтению данных, но не к записи данных; Lock относится к доступу к данным (то есть затрагивает и чтение и запись и модификацию); Можно сказать так, что механизм Lock обеспечивает работоспособность различных уровней изоляции данных, в самом тупом примере: если у вас стоит уровень изоляции READ_COMMITTED, то вставка произведенная параллельной сессией будет локирована на запись пока сессия не выдаст commit(), но лок на чтение будет открыт сразу при запросе вашего select, а при REPEATABLE_READ оба лока (на чтение и запись) будут стоять до commit() - соответственно ваш select увидит результаты модификации только после коммита. Каким способом в Hibernate можно указать требуемый уровень изоляции транзакции для конкретной транзакции? Вообще это не сильно здорово. Уровень изоляции транзакций это принадлежность коннекта к БД, соответственно фактически изменение уровня изоляции равносильно переконнекту к БД, а насколько это дорогая операция вы сами можете оценить исходя из имеющейся у вас ситуации. Тем не менее, если вы таки решили менять перед каждой транзакцией уровень изоляции транзакций, то самый простой способ это отправить в СУБД raw SQL query (любой ORM это поддерживает), типа: ALTER SESSION SET ISOLATION_LEVEL SERIALIZABLE

Ответ 2



Вопрос лёгкий, но ответ слишком длинный. Лучше книжки почитать. Уровни изоляции, блокировки настоящие и оптимистические - работают каждый над своей проблемой. Прямо они не связаны. Подбирая их комбинацию, получаете то, что Вам нужно. В jpa предусмотрены как оптимистичные режимы блокировки (полностью полагаются на @Version), так и пессимистичные (которые выполняются сервером базы данных): OPTIMISTIC (он же READ) OPTIMISTIC_FORCE_INCREMENT (он же WRITE) PESSIMISTIC_READ PESSIMISTIC_WRITE PESSIMISTIC_FORCE_INCREMENT Как изменить уровень изоляции. Если управляете транзакцией самостоятельно, то и меняйте как хотите (сonnection.setTransactionIsolation? Нужно только connection извлечь из entity из managera). А например JTA изменить вряд ли получится. Там уровень задаётся в конфигурации datasource и изменение его не предполагается. Кажется именно так. Хотя можете уточнить этот момент. Всё течёт, всё изменяется. Может это уже не так.

Ответ 3



Некоторую информацию по данному вопросу можно почерпнуть из статьи с хабра UPD по второму вопросу: Аннотация @Transactional указывает, что метод должен выполняться в транзакции. Менеджер транзакций открывает новую транзакцию и создаёт для неё экземпляр Session, который доступен через sessionFactory.getCurrentSession(). Все методы, которые вызываются в методе с данной аннотацией, также имеют доступ к этой транзакции, потому что экземпляр Session является переменной потока (ThreadLocal). Вызов sessionFactory.openSession() откроет совсем другую сессию, которая не связана с транзакцией. Параметр rollbackFor указывает исключения, при выбросе которых должен быть произведён откат транзакции. Есть обратный параметр — noRollbackFor, указывающий, что все исключения, кроме перечисленных, приводят к откату транзакции. Параметр propagation самый интересный. Он указывает принцип распространения транзакции. Может принимать любое значение из перечисления org.springframework.transaction.annotation.Propagation. Propagation.REQUIRED — выполняться в существующей транзакции, если она есть, иначе создавать новую. Propagation.MANDATORY — выполняться в существующей транзакции, если она есть, иначе генерировать исключение. Propagation.SUPPORTS — выполняться в существующей транзакции, если она есть, иначе выполняться вне транзакции. Propagation.NOT_SUPPORTED — всегда выполняться вне транзакции. Если есть существующая, то она будет остановлена. Propagation.REQUIRES_NEW — всегда выполняться в новой независимой транзакции. Если есть существующая, то она будет остановлена до окончания выполнения новой транзакции. Propagation.NESTED — если есть текущая транзакция, выполняться в новой, так называемой, вложенной транзакции. Если вложенная транзакция будет отменена, то это не повлияет на внешнюю транзакцию; если будет отменена внешняя транзакция, то будет отменена и вложенная. Если текущей транзакции нет, то просто создаётся новая. Propagation.NEVER — всегда выполнять вне транзакции, при наличии существующей генерировать исключение.

вторник, 16 июля 2019 г.

Java, Jms, JPA, Oracle создать уникальные строки

Надо брать данные из Jms и искать в таблице Address если такой есть тогда взять его ID если нет создать новый и достать его ID. Вот код:
try{ Query query = em.createQuery( "SELECT s.id FROM Address s WHERE s.counrtyId=?1 and s.districtId=?2 and s.regionId=?3 and s.city=upper(?4)") .setParameter(1, sa.getCounrtyId()) .setParameter(2, sa.getDistrictId()) .setParameter(3, sa.getRegionId()) .setParameter(4, sa.getCity());
Long result=(Long)query.getSingleResult(); return result; } catch (NoResultException e){ em.persist(entity); em.flush(); return sa.getId(); }
Так как jms обрабатывает сообщения параллельно он не находит в таблице такую строку создает новую в транзакций пока вся обработка не закончится только потом сохраняет. Если в параллельном потоке такая же строка он тоже в таблице ничего не находит и создает новую когда коммитит в базе появляются 2 таких строк. Как можно предотвратить дублирование строк?


Ответ

Создать в базе уникальный ключ по countryId, districtId, regionId, city (если это все поля, кроме id), тогда вместо вставки в базу дублирующей записи будет возникать исключение. Если доставка сообщения jms осуществляется в транзакции, то она будет автоматически отменена и позже будет вторая попытка доставить это же сообщение. Со второй попытки id будет обнаружен. Если доставка jms не окружена транзакцией, то можно отловить исключение вставки внутри метода, принимающего сообщение. Ещё можно синхронизовать обработку сообщений, сделав её не параллельной, а поочерёдной, если такой вариант приемлем.

суббота, 6 июля 2019 г.

Работа с БД при помощи JPA 2

Здравствуйте. Помогите, пожалуйста, разобраться, JPA 2. Вопрос вот в чем: Допустим, у меня есть некоторый класс, работающий с БД. Разные объекты его создают, используют и т.д. Чтобы не было много "мертвых" подключений, их постоянно надо закрывать. EntityManagerFactory emf = Persistence.createEntityManagerFactory("commonStorage"); EntityManager em = emf.createEntityManager(); // операции em.close(); emf.close(); Но создание нового EntityManager занимает время. Может, есть что-то, что я упустил? Может, надо использовать один EntityManagerFactory на все приложение? Или другие варианты.


Ответ

Да, EntityManagerFactory тяжеловесный объект и не стоит его создавать при каждом запросе. Лучше использовать стратегию - один EntityManager на один запрос.

среда, 13 марта 2019 г.

javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread

GenericDao.java:
@Repository public abstract class GenericDao implements GeneralDao {
private Class className;
public GenericDao(Class className) { this.className = className; }
@PersistenceContext private EntityManager entityManager;
public EntityManager getEntityManager() { return entityManager; }
@Override public void add(T object) { try { getEntityManager().persist(object); } catch (HibernateException e) { throw new DaoException(ErrorMessage.ADD_ENTITY_FAIL, e); } }
@Override public void update(T object) { try { getEntityManager().merge(object); } catch (HibernateException e) { throw new DaoException(ErrorMessage.UPDATE_ENTITY_FAIL, e); } }
@Override public void remove(T object) { try { getEntityManager().remove(object); } catch (HibernateException e) { throw new DaoException(ErrorMessage.REMOVE_ENTITY_FAIL, e); } }
@Override public T getById(int id) { try { return getEntityManager().find(this.className, id); } catch (HibernateException e) { throw new DaoException(ErrorMessage.GET_BY_ID_ENTITY_FAIL, e); } }
public abstract List getAll() throws DaoException;
}
GenericService.java
@Service public abstract class GenericService implements GeneralService { private static Logger logger = Logger.getLogger(GenericService.class);
@Autowired private GenericDao dao;
@Transactional @Override public void add(T object) throws ServiceException { try { dao.add(object); } catch (DaoException e) { logger.debug(e); throw new ServiceException(e.getMessage()); } }
@Transactional @Override public void update(T object) throws ServiceException { try { dao.update(object); } catch (DaoException e) { logger.debug(e); throw new ServiceException(e.getMessage()); } }
@Transactional @Override public void remove(T object) throws ServiceException { try { dao.remove(object); } catch (DaoException e) { logger.debug(e); throw new ServiceException(e.getMessage()); } }
@Transactional(readOnly = true) @Override public T getById(int id) throws ServiceException { try { return dao.getById(id); } catch (DaoException e) { logger.debug(e); throw new ServiceException(e.getMessage()); } }
@Transactional(readOnly = true) @Override public List getAll() throws ServiceException { try { return dao.getAll(); } catch (DaoException e) { logger.debug(e); throw new ServiceException(e.getMessage()); } }
}
UserServiceImpl.java:
@Service public class UserServiceImpl extends GenericService implements UserService { private static Logger logger = Logger.getLogger(UserServiceImpl.class);
@Autowired private UserDao userDao;
@Transactional @Override public String checkUser(String userLogin, String userPassword) throws ServiceException { String namePage = "errorAuthorization"; List userList; try { userList = userDao.getByLoginAndPassword(userLogin, userPassword); } catch (DaoException e) { logger.debug(e); throw new ServiceException(e.getMessage()); } if(userList.size() != 0) { return UserRoleChecker.defineUserPage(userList.get(0)); } return namePage; }
@Transactional @Override public void addUser(String userLogin, String userPassword, String userMail) throws ServiceException { Role role = new Role(0L, RoleType.USER); User user = new User(0L, userLogin, userPassword, userMail, role); add(user); }
}
UserController.java:
@Controller public class UserController { private static String className = UserController.class.getName(); private static Logger logger = Logger.getLogger(UserController.class.getName());
@Autowired private UserService userService;
@RequestMapping(value = "/check_user", method = RequestMethod.POST) public ModelAndView authorizationUser(HttpServletRequest request, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView(); String returnPage; try { returnPage = userService.checkUser(request.getParameter(RequestParameter.USER_LOGIN), request.getParameter(RequestParameter.USER_PASSWORD)); } catch (ServiceException e) { logger.debug(e); returnPage = ErrorHandler.returnErrorPage(e.getMessage(), className); } modelAndView.setViewName(returnPage); return modelAndView; }
@RequestMapping(value = "/add_user", method = RequestMethod.POST) public ModelAndView registrationUser(HttpServletRequest request, HttpServletResponse response) { ModelAndView modelAndView = new ModelAndView(); String returnPage = Page.SUCCESSFUL_REGISTRATION; try { userService.addUser(request.getParameter(RequestParameter.USER_LOGIN), request.getParameter(RequestParameter.USER_PASSWORD), request.getParameter(RequestParameter.USER_MAIL)); } catch (ServiceException e) { logger.debug(e); returnPage = ErrorHandler.returnErrorPage(e.getMessage(), className); } modelAndView.setViewName(returnPage); return modelAndView; }
}
root-context.xml:




org.hibernate.dialect.MySQLDialect true true 2 true update





logs:
org.springframework.web.servlet.FrameworkServlet 2017-05-10 22:23:59,107 DEBUG - Could not complete request javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:282) at com.sun.proxy.$Proxy27.persist(Unknown Source) at by.netcracker.artemyev.dao.GenericDao.add(GenericDao.java:35) at by.netcracker.artemyev.service.GenericService.add(GenericService.java:24) at by.netcracker.artemyev.service.impl.UserServiceImpl.addUser(UserServiceImpl.java:48) at by.netcracker.artemyev.web.UserController.registrationUser(UserController.java:45) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) at javax.servlet.http.HttpServlet.service(HttpServlet.java:661) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:742) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:475) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:495) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:767) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1354) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:745)
Почему возникает эта ошибка и как её исправить?


Ответ

Проблема в том, что spring не строит прокси-объект поверх UserServiceImpl, а внедряет его напрямую в UserController, что видно из твоего исключения.
Причины, я полагаю, в том, что твой root-context.xml относится к контексту всего приложения, т.е. инициализируется через ContextLoaderListener. Внутри указанного контекста UserServiceImpl имеет транзакционность. Однако, у тебя также имеется некая конфигурация (тут лучше бы приложить web.xml) относящаяся к DispatcherServlet, в которой инициализируется UserController и как раз нет tx:annotation-driven. Соответственно в UserController попадет чистый бин, а не его прокси.
Быстро решить данную проблему можно убрав @Service с самого класса UserServiceImpl и определив его в root-context.xml

среда, 20 февраля 2019 г.

Использование criteria из JPA

В новом Hibernate использование Criteria является Deprecated. Как я могу заменить такой код с использованием org.hibernate.Criteria
Criteria criteria = session.createCriteria(Role.class); long id = ((Role) criteria.add(Restrictions.eq("name", name)).uniqueResult()).getId();
на его эквивалент с использованием javax.persistence.criteria.CriteriaBuilder из JPA ?


Ответ

em - экземпляр EntityManager
public Long query(String name) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery cq = cb.createQuery(Role.class); Root root = cq.from(Role.class); cq.where(cb.equal(root.get("name"), cb.parameter(String.class, "name"))); // cq.distinct(true); Query q = em.createQuery(cq); q.setParameter("name", name); List result = q.getResultList(); if (result.size() > 0) { return result.get(0).getId(); } return null; }

четверг, 29 ноября 2018 г.

Сохранение цен в логах

Есть таблица товаров (Goods). У каждого товара есть цена. При создании заказа (Order) к нему прикрепляются товары с помощью дополнительной таблицы Goods__Order. При этом после заказа цены товаров в заказе не должны меняться, даже если они изменились в таблице Goods.
Один из вариантов - сохранять цену в таблицу Goods__Order. Но количество полей, которые не должны меняться, возможно, станет больше, и поэтому не хочется их все переносить в эту таблицу + вариант не очень универсальный.
Второй вариант, как мне кажется, можно логировать изменения в таблице Goods в таблицу Goods_audit, которая будет содержать изменения полей + номер версии (revision number). Это доступно через Hibernate Envers. Но тогда в таблицу Goods_Order нужно будет вносить id товара + номер версии + я не нашел примеров, что так делают.
Какой вариант будет лучше? Учитывая, огромное количество интернет-магазинов, наверное уже есть более-менее известное решение для этого. Да и во вариантах на php никакого Hibernate Envers нет.


Ответ

Заказ не является объектом бухгалтерского учета, это объект процессов логистики и взаимоотношения с клиентами. Однозначных рекомендаций по логистике и CRM не существует, правильная постановка таких процессов является ноухау организации. Пока заказ не оплачен, он является по сути предложением клиенту и объектом любой переоценки. В магазине могут действовать правила расчета цены отдельного товара для клиента с учетом скидок (иногда и надбавок) - скидка по промокоду, скидка за объем заказа (суммарный по всем товарным позициям заказа), персональная скидка клиента (например - за историю заказов, либо скидка отдельным категориям клиентов), и т.д. За время, когда заказ ожидает оплаты, может стартовать кампания по стимулированию спроса - на отдельные товары/группы начинает действовать значительная скидка, которая отменяет другие скидки (промокод, песональные и т.д.). После того, как заказ оплачен (тем более - если исполнен), все текущие условия - базовая цена, скидки, условия доставки и пр. - должны быть зафиксированы и не подлежать дальнейшим изменениям. В развитых eComm платформах типа Magento текущие цены ведутся в отдельных объектах - прайс-листах, могут настраиваться правила перерасчета цен (скажем, в зависимости от валютных котировок для импортных товаров), ведется история изменения прайс-листов. Правила перерасчета неоплаченных заказов также могут настраиваться.
Так должно быть, а реализуется ли это в конкретной eComm и как - зависит от платформы. Если сами пишете платформу - посоветуйтесь с отделом продаж :-)

суббота, 13 октября 2018 г.

Стоит ли делать составной первичный ключ?

Изучаю вот этот вопрос, там такая фраза в ответе
Composite primary keys typically arise when mapping from legacy databases when the database key is comprised of several columns.
Я правильно понимаю, что legacy это практически синоним слову "устаревший"? Если так, то стоит ли, при разработке нового приложения и новой базы данных, делать составные ключи? Или просто добавить ещё один столбец, который по сути ничего не будет делать, ведь всю работу с бд обычно выполняют фреймворки?


Ответ

Это довольно холиварный вопрос, и единственно верного ответа тут нет. Но чаще всего мнения сходятся к тому, что ключи должны быть не составными, и в них не должно храниться какой-либо информации о сущности. То есть это должно быть какое-то (обычно целочисленное автоматически инкрементируемое) поле, обеспечивающее уникальность записи

пятница, 5 октября 2018 г.

Уровень изоляции транзакций и Hibernate

Первый вопрос. Какая связь существует между уровнем изоляции транзакций, параметрами JPA Lock.READ / Lock.WRITE и версиями @Version (Оптимистическая блокировка)? Я более менее понял по отдельности каждую из этих 3 разделов, но не понимаю, как они связаны между собой. Выбранный уровень изоляции транзакций дает некие «гарантии» при проведении операций с таблицами. Параметры Lock.READ/Lock.WRITE позволяют добиться блокировки полей таблицы при проведении операций. Если я правильно понял, для этого как раз и используются поле @Version. Но все таки не до конца улавливаю связь между эти тремя понятиями. Или они дополняют друг друга, или это разные способы решения одних и тех же проблем. В общем, буду очень благодарен за ответ или же ссылку, где можно прочесть информацию именно по вопросы связи между этими понятиями и их совместном использовании. Второй вопрос. Каким способом в Hibernate можно указать требуемый уровень изоляции транзакции для конкретной транзакции? И принято ли так делать? Спасибо.


Ответ

Попробую ответить по порядку:
Как соотносятся Lock и уровни изоляции
Уровень изоляции транзакций (transaction isolation level) - относится к чтению данных, но не к записи данных; Lock относится к доступу к данным (то есть затрагивает и чтение и запись и модификацию); Можно сказать так, что механизм Lock обеспечивает работоспособность различных уровней изоляции данных, в самом тупом примере: если у вас стоит уровень изоляции READ_COMMITTED, то вставка произведенная параллельной сессией будет локирована на запись пока сессия не выдаст commit(), но лок на чтение будет открыт сразу при запросе вашего select, а при REPEATABLE_READ оба лока (на чтение и запись) будут стоять до commit() - соответственно ваш select увидит результаты модификации только после коммита.
Каким способом в Hibernate можно указать требуемый уровень изоляции транзакции для конкретной транзакции?
Вообще это не сильно здорово. Уровень изоляции транзакций это принадлежность коннекта к БД, соответственно фактически изменение уровня изоляции равносильно переконнекту к БД, а насколько это дорогая операция вы сами можете оценить исходя из имеющейся у вас ситуации.
Тем не менее, если вы таки решили менять перед каждой транзакцией уровень изоляции транзакций, то самый простой способ это отправить в СУБД raw SQL query (любой ORM это поддерживает), типа:
ALTER SESSION SET ISOLATION_LEVEL SERIALIZABLE