I use SpringBoot and Hibernate.
I have the following entities:
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_id")
private Long id;
private String productName;
private Long productPrice;
private String productDescription;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "product_type_id")
private ProductType productType;
//getters and setters
and
#Entity
#Table(name = "product_type")
public class ProductType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_type_id")
private Long id;
private String productTypeName;
private String productTypeDescription;
#OneToMany(mappedBy = "productType", cascade = CascadeType.ALL)
private List<Product> products;
//getters and setters
I have the following service:
#Service
public class ProductService {
ProductRepository productRepository;
ProductTypeRepository productTypeRepository;
public ProductService(ProductRepository productRepository, ProductTypeRepository productTypeRepository) {
this.productRepository = productRepository;
this.productTypeRepository = productTypeRepository;
}
#Transactional
public Product saveProduct(Product product){
return productRepository.save(product);
}
#Transactional
public List<Product> findAllByProductTypeName(String type){
ProductType productType = productTypeRepository.findByProductTypeName(type);
return productType.getProducts();
}
and at last, I have the following simple test to understand everything is working:
#SpringBootTest
class ProductServiceTest extends BaseDAOTest {
#Autowired
ProductService productService;
#Autowired
ProductTypeService productTypeService;
#Test
void findAllByProductTypeTest(){
Product product = new Product("Test product", 100L, "Test product description");
product.setProductType(new ProductType("Test type", "Test description"));
productService.saveProduct(product);
List<Product> productList = productService.findAllByProductTypeName("Test type");
productList.get(0);
}
}
Here productList.get(0) i have the following error:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.krasovsky.warehouse.models.ProductType.products, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
at org.hibernate.collection.internal.PersistentBag.get(PersistentBag.java:561)
What is the correct way to address this issue? Say if you need any additional info. Thanks in advance.
You are trying to do a fetch outside of a transaction. You need to annotate your test class with #DataJpaTest:
Data JPA tests are transactional and rollback at the end of each test by default
https://docs.spring.io/spring-boot/docs/1.5.2.RELEASE/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test
Related
I want to implement a simple uni-directional one-to-many association. I have tried the below setup. But when I fetch the Tenant entity, I always see null for the subscriptions field.
#Getter
#Setter
#Entity
#ToString
public class Tenant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String tenantKey;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name="tenantKey")
private Set<Subscription> subscriptions;
public Set<Subscription> getSubscriptions() {
return subscriptions;
}
public void setSubscriptions(Set<Subscription> subscriptions) {
this.subscriptions = subscriptions;
}
}
and below is the Subscription entity
#Entity
#Getter
#Setter
#ToString
public class Subscription {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String tenantKey;
private String serviceName;
}
Below is my repository class which I fetch the Tenant object
#ApplicationScoped
public class TenantRepository implements PanacheRepository<Tenant> {
public Uni<Tenant> findByTenantKey(String tenantKey) {
return find("tenantKey= ?1", tenantKey).firstResult();
}
}
I'm a beginner in spring boot, try to make an app with class Department & Employee. I make the relation between these two classes, Department can have many Employee whereas Employee can only have one Department. Every time I ended with an error:
com.learning.model.Employee cannot be converted to java.lang.Integer
Also, I've found two ways to inserting data into DB via API. First through the service layer, 2nd directly through the controller. Thankful if you could advise as to what is the most authentic method among the two above.
Department.java
#Entity
#Table(name = "Department")
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "dept_seq")
#SequenceGenerator(initialValue = 1, name = "dept_seq", sequenceName = "dept_sequence")
#Column(name = "id")
private Integer id;
#Column(name = "deptName")
private String deptName;
#OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonIgnore
private List<Employee> employees;
}
Employee.java
#Entity
#Table(name = "Employee_Dtls")
#ToString
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "emp_seq")
#SequenceGenerator(initialValue = 1, name = "emp_seq", sequenceName = "employee_sequence")
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "dept_id")
private Department department;
}
DepartmentService.java
#Service
public class DepartmentService {
#Autowired
private EmployeeRepository employeeRepository;
#Autowired
private DepartmentRepository departmentRepository;
//Get Department
public List<Department> getAllDepartments() {
return departmentRepository.findAll();
}
//Add Department
public Department addDepartment(Department department) {
Employee emp = employeeRepository.findById(department.getEmployees().get(department.getId())).orElse(null);
if (null == emp) {
emp = new Employee();
}
emp.setName(department.getEmployees().get(emp.getId()));
department.setEmployees(emp);
return departmentRepository.save(department);
}
}
DepartmentController.java
public class DepartmentController {
#Autowired
private DepartmentService departmentService;
#GetMapping("/get-departments")
public ResponseEntity<List<Department>> getAllDepartments() {
List<Department> departments = departmentService.getAllDepartments();
return new ResponseEntity<>(departments, HttpStatus.OK);
}
#PostMapping("/department")
public ResponseEntity<Department> saveDepartment(#RequestBody Department department) {
Department dept = departmentService.addDepartment(department);
return new ResponseEntity<>(dept, HttpStatus.OK);
}
}
I think you must go over these stacks, these will probably help you to understand how #onetomany annotation works in spring
One to Many Relationship in spring boot REST Api
POSTing oneToMany in a REST call via Spring Boot API
I have the two entities with a manyToMany relationship:
#Entity
#Table(name="categories")
public class CategoryEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private int categoryId;
#Column(name="name")
private String CategoryName;
#ManyToMany(mappedBy = "categories")
private List<ProductEntity> products = new ArrayList<ProductEntity>();
}
#Entity
#Table(name="products")
public class ProductEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Integer productId;
#Column(name="name")
private String productName;
#Column(name="description")
private String description;
#Column(name="price")
private Float price;
#Column(name="rating")
private Float rating;
#Column(name="image")
private String image;
#Column(name="quantity")
private Integer quantity;
#ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "product_category",
joinColumns = {#JoinColumn(name = "product_id")},
inverseJoinColumns = {#JoinColumn(name = "category_id")}
)
private List<CategoryEntity> categories = new ArrayList<>();
}
In the database I have a join Table product_category that hold the product_id and category_id.
my question is how to add element to the joinTable product_category? is it possible to create a Repository even if we don't have an entities??
I tried this with my controller:
public class ProductController {
#Autowired
private ProductService productService;
#Autowired
private ProductMapper productMapper;
#Autowired
private CategoryMapper categoryMapper;
#Autowired
private CategoryService categoryService;
#Autowired
private ProductReviewService reviewService;
#Autowired
private ProductReviewMapper reviewMapper;
#PostMapping("/products")
public ResponseEntity<ProductDto> createProduct(#RequestBody ProductDto productDto) {
ProductEntity productEntity=productMapper.dtoToEntity(productDto);
for(CategoryDto categoryDto:productDto.getCategories()){
CategoryEntity categoryEntity=categoryMapper.dtoToEntity(categoryDto);
productEntity.getCategories().add(categoryEntity);
}
productEntity=productService.saveProduct(productEntity);
productDto.setProductId(productEntity.getProductId());
return ResponseEntity.created(null).body(productDto);
}
}
but I got this:
org.hibernate.PersistentObjectException: detached entity passed to persist: com.be.ec.entities.CategoryEntity
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:127) ~[hibernate-core-5.4.8.Final.jar:5.4.8.Final]
at
You have relationship consistency issue. you are adding a category to a product but not adding product into category
add this method into your ProductEntity class:
public void addCategory(CategoryEntity category) {
this.getCategories().add(category);
category.getProducts().add(this);
}
and use this method to add category into product.
ProductEntity productEntity=productMapper.dtoToEntity(productDto);
for(CategoryDto categoryDto:productDto.getCategories()){
CategoryEntity categoryEntity=categoryMapper.dtoToEntity(categoryDto);
productEntity.addCategory(categoryEntity); //changed line
}
productEntity=productService.saveProduct(productEntity);
productDto.setProductId(productEntity.getProductId());
I am trying to save a "User" object that is related by a OneToMany relationship to a "Volunteer" object.
When I try to save it, it only works when I provide the primary IDs for both these objects. However, what I need is to save the entity and let the database dictate the ID's via autoIncrement. I am not sure how am I suppose to do this or even if it's possible.
Json Mapping that works:
{
"id":8,
"userName": "user8",
"password": "pass1234",
"volunteersId": 6,
"volunteers": [{
"id":6,
"committeesId": 2,
"outreachDate": "2019-12-07",
"usersId": 8
}]
}
Json Mapping that I need (but will not work):
{
"userName": "user8",
"password": "pass1234",
"volunteersId": 6,
"volunteers": [{
"committeesId": 2,
"outreachDate": "2019-12-07",
}]
}
So I am thinking maybe there's a way to connect the foreign keys so that I wont have to explicitly add the autoIncrement IDs (usersId, volunteersId).
User controller:
#Controller
public class UserController {
#RequestMapping(value = "/v1/users", method = RequestMethod.POST)
public ResponseEntity<Object> saveUsers( #RequestBody UserEntity request){
try {
return ResponseEntity.ok(userService.saveUser(request));
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}
User service:
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
public Page<UserEntity> saveUser(UserEntity user){
userRepository.save(user);
Pageable pageable = PageRequest.of(0, 10, Sort.by("id").descending());
return userRepository.findAll(pageable);
}
}
User Repository:
public interface UserRepository extends JpaRepository<UserEntity, Long> {
public List<UserEntity> findAllByOrderByIdAsc();
public List<UserEntity> findAllByOrderByIdDesc();
public Page<UserEntity> findByUserNameContaining(String userName, Pageable pageable);
}
User Entity:
#Entity
#Table(name = "users")
public class UserEntity {
#Id
private long id;
#Column(name = "username")
private String userName;
private String password;
#Column(name = "volunteers_id", nullable = true)
private Long volunteersId;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "users_id")
private List<VolunteerEntity> volunteers = new ArrayList<>();
// omitted getters and setters
}
Volunteer Entity:
#Entity
#Table(name = "volunteers")
public class VolunteerEntity {
#Id
private long id;
#Column(name = "committees_id")
private long committeesId;
#Column(name = "outreach_date")
private Date outreachDate;
#Column(name = "users_id")
private Long usersId;
// omitted getters and setters
}
Any ideas or suggestions how to save this whole entity? I am wondering if this is really possible to save as in one whole process. Though if not, I am thinking of just saving them independently (User info first, then Volunteer next) but just in case it would be possible, it would really be a great help
You need to add #GeneratedValue anotation next to the #id
#Id
#GeneratedValue(strategy = GenerationType.selectOne)
private long id;
In case of sequence you need to add an extra anotation
#Id
#SequenceGenerator(name = "customName", sequenceName = "sequenceNameInDatabase")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="customName")
private long id;
This will make the primary id generation auto
#Entity
#Table(name = "user")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
I have two entities lets call them Categories and Products. These two entities are mapped by a many to many relationship.
My problem is that i am trying to get category information from products. Trying this results in empty categories.
This is my code :
PersistenceEntity
#MappedSuperclass
public class PersistenceEntity implements Serializable {
private static final long serialVersionUID = 4056818895685613967L;
// Instance Variables
#Id
#Column(unique = true)
#GeneratedValue(strategy = GenerationType.TABLE)
protected Long id;
#JsonIgnore
#Temporal(javax.persistence.TemporalType.TIMESTAMP)
protected Date creationDate = new Date();
...Getters and Setters omitted for brevity
}
Category
#Entity
#Table(name = "category")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Category extends PersistenceEntity{
private static final long serialVersionUID = 1L;
#Column(nullable = false)
private String categoryName;
#Column(nullable = false)
private Boolean active;
#Column(nullable = true)
private String picture;
#JsonIgnore
private MetaData metadata;
#ManyToMany(fetch = FetchType.EAGER,mappedBy = "categories")
private Set<Product> products;
...Getters and Setters omitted for brevity
}
Product
#Entity
#Table(name = "products",uniqueConstraints = { #UniqueConstraint(columnNames = "productCode")})
#JsonIgnoreProperties(ignoreUnknown = true)
public class Product extends PersistenceEntity {
private static final long serialVersionUID = 8727166810127029053L;
#Column(name = "product_name")
private String name;
private String productImageUrl;
#JsonIgnore
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name="category_products",
joinColumns={#JoinColumn(name="product_id", unique = false)},
inverseJoinColumns={#JoinColumn(name="category_id", unique = false)})
private Set<Category> categories;
...Getters and Setters omitted for brevity
}
ProductServiceImplementation
#Service
public class ProductService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
private ProductRepository productRepository;
public List<Product> getProductsByShopId( Long id) {
List<Product> productList = new ArrayList<>();
productList = productRepository.findByShopId(id);
return productList;
}
public Set<Long> getCategoryIds(List<Product> products){
Set<Long> categoriesIDs = new HashSet<Long>();
for (Product product : products) {
product.getCategories().forEach(category -> {
categoriesIDs.add(category.getId());
});
}
return categoriesIDs;
}
}
The problem is getting the categoryIds that are mapped to the list of products.
How can i get CategoryIds from Product. My getCategoryIds function returns empty always
public Set<Long> getCategoryIds(List<Product> products){
Set<Long> categoriesIDs = new HashSet<Long>();
for (Product product : products) {
product.getCategories().forEach(category -> {
categoriesIDs.add(category.getId());
});
}
return categoriesIDs;
}