I'm trying to create a product catalogue that has Product -> Attributes -> Options
So the data would look something like this:
product_one
attribute_one
option_one
option_two
attribute_two
option_one
option_two
The code is available on GitHub https://github.com/ccsalway/prod_info_mngr
I have created a Class for each Entity:
@Entity
class Product {
@Id
@Generatedvalue
private Long id;
private String name;
// getters and setters
}
@Entity
class Attribute {
@Id
@Generatedvalue
private Long id;
private String name;
@ManyToOne(cascade = CascadeType.REMOVE)
private Product product;
// getters and setters
}
@Entity
class Option {
@Id
@Generatedvalue
private Long id;
private String name;
@ManyToOne(cascade = CascadeType.REMOVE)
private Attribute attribute;
// getters and setters
}
I have created a Repository for each Entity:
@Repository
public interface ProductRepository extends PagingAndSortingRepository<Product, Long> {
}
@Repository
public interface AttributeRepository extends PagingAndSortingRepository<Attribute, Long> {
}
@Repository
public interface OptionRepository extends PagingAndSortingRepository<Option, Long> {
}
I have created a Service for each Entity:
@Service
public class ProductService {
// Autowired Repositories
// methods
}
@Service
public class AttributeService {
// Autowired Repositories
// methods
}
@Service
public class OptionService {
// Autowired Repositories
// methods
}
I have created a Controller for each Entity:
@Controller
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
//methods
}
@Controller
@RequestMapping("/product/{prod_id}/attribute")
public class AttributeController{
@Autowired
private AttributeService attributeService;
//methods
}
@Controller
@RequestMapping("/product/{prod_id}/attribute/{attr_id}")
public class OptionController {
@Autowired
private OptionService optionService;
//methods
}
And (finally) I have created several views for each Controller (I won't add them here).
What I am trying to do in the product_view.jsp
View is show a list of attributes and their associated options, something like this:
<table id="attrTable">
<thead>
<tr>
<th>Name</th>
<th>Options</th>
</tr>
</thead>
<tbody>
<c:forEach items="${attributes}" var="attr">
<tr data-id="${attr.id}">
<td>${fn:htmlEscape(attr.name)}</td>
<td><c:forEach items="${attr.options}" var="opt" varStatus="loop">
${opt.name}<c:if test="${!loop.last}">,</c:if>
</c:forEach></td>
</tr>
</c:forEach>
</tbody>
</table>
So the table would look something like this
product_one
attribute_one option_one, option_two
attribute_two option_one, option_two
The first step was to create a @RequestMapping
in ProductController
:
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String view(Model model, @PathVariable Long id) {
Product product = productService.getProduct(id);
List<Attribute> attributes = productService.getAttributes(product);
model.addAttribute("product", product);
model.addAttribute("attributes", attributes);
return "products/product_view";
}
but ${attr.options}
in the View doesn't recognise the options
key so how do I go about making this work?
I tried to add a @OneToMany
association in the Product
Entity but this created a table in the database with product_id|attribute_id
which you then have to save the attribute, and then update the product with the new attribute, which also means when you select a product, you are pulling ALL attributes and ALL options which prevents Paging on those.
@Entity
class Product {
@Id
@Generatedvalue
private Long id;
private String name;
@OneToMany
List<Attribute> attributes;
// getters and setters
}
I found a solution:
I added OneToMany
and ManyToOne
relationships as follows. This method also allowed the repository.delete(id)
method to cascade delete.
FetchType.LAZY tells Spring to only fetch the underlying item(s) when requested. For example, when a request for a
Product
is made, theid
andname
will be fetched, but because theattributes
are@OneToMany
which is, by default, LAZY, theattributes
won't be fetched from the DB until a specific call toproduct.getAttributes()
is made.
@Entity
public class Product {
@Id
@GeneratedValue
private Long id;
private String name;
// OneToMany is, by default, a LAZY fetch
@OneToMany(cascade = CascadeType.ALL, mappedBy = "product")
private Set<Attribute> attributes = new HashSet<>();
// getters and setters
}
@Entity
public class Attribute {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id", nullable = false)
private Product product;
// OneToMany is, by default, a LAZY fetch
@OneToMany(cascade = CascadeType.ALL, mappedBy = "attribute")
private Set<Option> options = new HashSet<>();
// getters and setters
}
@Entity
public class Option {
@Id
@GeneratedValue
private Long id;
@NotEmpty
@Size(min = 1, max = 32)
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "attribute_id", nullable = false)
private Attribute attribute;
// getters and setters
}
In the ProductController
, I separate the Attributes
from the Product
so that I can use Paging on the Attributes (whereas if I just called product.getAttributes()
, it would fetch ALL the Attributes)
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String view(Model model, @PathVariable Long id, @RequestParam(name = "page", defaultValue = "0") int page) throws ProductNotFoundException {
Product product = productService.getProduct(id);
model.addAttribute("product", product);
// requesting attributes separately (as opposed to using LAZY) allows you to use paging
Page<Attribute> attributes = productService.getAttributes(product, new PageRequest(page, 10));
model.addAttribute("attributes", attributes);
return "products/product_view";
}
Then in the view, I remember to loop over the attributes
and not the product.attributes
as explained above.
Because the options
property is set to LAZY
in the Attribute
Entity, when the loop calls attr.options
, Spring will make a request to the DB for the Options
for the current Attribute
.
<table id="attrTable" class="table is-hoverable is-striped is-fullwidth" style="cursor:pointer;">
<thead>
<tr>
<th>Name</th>
<th>Options</th>
</tr>
</thead>
<tbody>
<c:forEach items="${attributes}" var="attr">
<tr data-id="${attr.id}">
<td>${fn:htmlEscape(attr.name)}</td>
<td>
<c:forEach items="${attr.options}" var="opt" varStatus="loop">
${opt.name}<c:if test="${!loop.last}">,</c:if>
</c:forEach>
</td>
</tr>
</c:forEach>
</tbody>
</table>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.