【发布时间】:2020-12-02 11:43:52
【问题描述】:
我是使用数据库的新手,并且我有一个带有 HSQLDB 的 Spring DATA JPA 培训项目,该项目有 2 个具有子集合和多对多关系的实体。 一个实体称为菜单,它包含一个 Dish 列表,反之亦然,一个 Dish 包含一组它所属的菜单。 当我尝试将一组菜肴插入表中时,Hibernate 会为每个实体发送一个单独的调用。有什么方法可以优化 Hibernate 以发送一个复杂的查询来保存整个子集合? UPD。 抱歉这个错误,实际上我正在保存包含 Dish 集合的菜单,而不仅仅是保存 Dish 集合。
Hibernate:
call next value for global_seq
Hibernate:
/* insert com.atanava.restaurants.model.Menu
*/ insert
into
menus
(date, restaurant_id, id)
values
(?, ?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
Hibernate:
/* insert collection
row com.atanava.restaurants.model.Menu.dishes */ insert
into
dishes_menus
(menu_id, dish_id)
values
(?, ?)
菜单类:
@NamedQueries({
@NamedQuery(name = Menu.GET, query = "SELECT m FROM Menu m WHERE m.id=:id AND m.restaurant.id=:restaurantId"),
@NamedQuery(name = Menu.BY_RESTAURANT, query = "SELECT m FROM Menu m WHERE m.restaurant.id=:restaurantId ORDER BY m.date DESC"),
@NamedQuery(name = Menu.BY_DATE, query = "SELECT m FROM Menu m WHERE m.date=:date"),
@NamedQuery(name = Menu.BY_REST_AND_DATE, query = "SELECT m FROM Menu m WHERE m.restaurant.id=:restaurantId AND m.date=:date"),
@NamedQuery(name = Menu.DELETE, query = "DELETE FROM Menu m WHERE m.id=:id AND m.restaurant.id=:restaurantId"),
})
@Entity
@Table(name = "menus", uniqueConstraints = {@UniqueConstraint(columnNames = {"restaurant_id", "date"},
name = "restaurant_id_date_idx")})
public class Menu extends AbstractBaseEntity {
public static final String GET = "Menu.get";
public static final String BY_RESTAURANT = "Menu.getAllByRestaurant";
public static final String BY_DATE = "Menu.getAllByDate";
public static final String BY_REST_AND_DATE = "Menu.getByRestAndDate";
public static final String DELETE = "Menu.delete";
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "restaurant_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@NotNull
private Restaurant restaurant;
@ManyToMany(fetch = FetchType.EAGER)
@Fetch(FetchMode.SUBSELECT)
@JoinTable(name = "dishes_menus",
joinColumns = @JoinColumn(name = "menu_id"),
inverseJoinColumns = @JoinColumn(name = "dish_id"))
private List<Dish> dishes;
@Column(name = "date", columnDefinition = "date default current_date", nullable = false)
@NotNull
private LocalDate date;
//constructors, getters, setters
}
菜品类:
@NamedQueries({
@NamedQuery(name = Dish.BY_RESTAURANT, query = "SELECT d FROM Dish d WHERE d.restaurant.id=:restaurantId ORDER BY d.name"),
})
@Entity
@Table(name = "dishes", uniqueConstraints = {@UniqueConstraint(columnNames = {"restaurant_id", "name"},
name = "unique_restaurant_id_dish_name_idx")})
public class Dish extends AbstractNamedEntity {
public static final String BY_RESTAURANT = "Dish.getAllByRestaurant";
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "restaurant_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@NotNull
private Restaurant restaurant;
@Column(name = "price", nullable = false)
@NotNull
private Integer price;
@Column(name = "active", nullable = false, columnDefinition = "bool default true")
private boolean active = true;
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "dishes")
Set<Menu> menus;
//constructors, getters, setters
}
我尚未覆盖保存方法的菜单存储库: 我用原始方法保存菜单 crudMenuRepository.save(菜单)
@Transactional(readOnly = true)
public interface CrudMenuRepository extends JpaRepository<Menu, Integer> {
@Transactional
@Modifying
@Query(name = Menu.DELETE)
int delete(@Param("id") int id, @Param("restaurantId") int restaurantId);
@Query(name = Menu.GET)
Menu get(@Param("id") int id, @Param("restaurantId") int restaurantId);
@Query(name = Menu.BY_REST_AND_DATE)
Menu getByRestAndDate(@Param("restaurantId") int restaurantId, @Param("date") LocalDate date);
@Query(name = Menu.BY_RESTAURANT)
List<Menu> getAllByRestaurant(@Param("restaurantId") int restaurantId);
@Query(name = Menu.BY_DATE)
List<Menu> getAllByDate(@Param("date") LocalDate date);
}
initDB.sql 文件片段:
CREATE TABLE dishes
(
id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
restaurant_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
price INTEGER NOT NULL,
active BOOLEAN DEFAULT TRUE NOT NULL,
FOREIGN KEY (restaurant_id) REFERENCES restaurants (id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX unique_restaurant_id_dish_name_idx on dishes (restaurant_id, name);
CREATE TABLE menus
(
id INTEGER GENERATED BY DEFAULT AS SEQUENCE GLOBAL_SEQ PRIMARY KEY,
restaurant_id INTEGER NOT NULL,
date DATE DEFAULT CURRENT_DATE NOT NULL,
CONSTRAINT restaurant_id_date_idx UNIQUE (restaurant_id, date),
FOREIGN KEY (restaurant_id) REFERENCES restaurants (id) ON DELETE CASCADE
);
CREATE TABLE dishes_menus
(
dish_id INTEGER NOT NULL,
menu_id INTEGER NOT NULL,
CONSTRAINT dish_id_menu_id_idx UNIQUE (dish_id, menu_id),
FOREIGN KEY (dish_id) REFERENCES dishes (id) ON DELETE CASCADE,
FOREIGN KEY (menu_id) REFERENCES menus (id) ON DELETE CASCADE
);
UPD2 这是我的 spring-db.xml 配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jdbc:initialize-database data-source="dataSource" enabled="${database.init}">
<jdbc:script location="${jdbc.initLocation}"/>
<jdbc:script encoding="utf-8" location="classpath:db/populateDB.sql"/>
</jdbc:initialize-database>
<context:property-placeholder location="classpath:db/hsqldb.properties" system-properties-mode="OVERRIDE"/>
<!--no pooling-->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="org.hsqldb.jdbcDriver"
p:url="${database.url}"
p:username="${database.username}"
p:password="${database.password}"/>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:dataSource-ref="dataSource"
p:packagesToScan="com.atanava.**.model">
<!--p:persistenceUnitName="persistenceUnit">-->
<property name="jpaPropertyMap">
<map>
<entry key="#{T(org.hibernate.cfg.AvailableSettings).FORMAT_SQL}" value="${hibernate.format_sql}"/>
<entry key="#{T(org.hibernate.cfg.AvailableSettings).USE_SQL_COMMENTS}" value="${hibernate.use_sql_comments}"/>
<entry key="#{T(org.hibernate.cfg.AvailableSettings).JPA_PROXY_COMPLIANCE}" value="false"/>
<!--<entry key="#{T(org.hibernate.cfg.AvailableSettings).HBM2DDL_AUTO}" value="${hibernate.hbm2ddl.auto}"/>-->
</map>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" p:showSql="${jpa.showSql}">
</bean>
</property>
</bean>
<tx:annotation-driven/>
<!-- Transaction manager for a single JPA EntityManagerFactory (alternative to JTA) -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"/>
<context:component-scan base-package="com.atanava.**.repository**"/>
<jpa:repositories base-package="com.atanava.**.repository**"/>
</beans>
这是我创建的 persistence.xml 文件:
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="menu-persistence-unit">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>com.atanava.restaurants.model.Menu</class>
<class>com.atanava.restaurants.model.Dish</class>
<properties>
<property name="hibernate.jdbc.batch_size" value="20"/>
</properties>
</persistence-unit>
</persistence>
【问题讨论】:
-
为了有效地使用 Hibernate,你应该看看 Vlad Mihalcea 的页面。他用
Set描述了ManyToMany relationship。对于很多实体的插入优化,可以看一下batch processing的文章。 -
菲利克斯,谢谢你的建议。我这样重构它:@ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.REMOVE, CascadeType.MERGE }) @JoinTable(name = "dishes_menus", joinColumns = @JoinColumn(name = "menu_id"), inverseJoinColumns = @JoinColumn(name = "dish_id")) private Set
菜;但是 Hibernate 仍然会发送大量的插入请求。 -
首先,我不确定 Eager fetching。只需始终使用延迟加载并在需要时初始化集合。由于我没有尝试您的整个代码库,因此我只能指出我的观察结果。我意识到您在
Dish类中使用了Set<Menu>。但是,在Menu类中,您使用List<Dish>。我的想法是首先删除 Hibernate 可以自动创建的一些东西,例如手动数据库初始化。然后,可能更容易找到原因。
标签: java hibernate spring-data-jpa