How to reference a properties value inside the schema attribute of an entity? - spring

There is an entity :
#Entity
#Table(name = "ITMMASTER" , schema = "TOMCTB")
public class Article {
#Id
#Column(name = "ITMREF_0")
private String code_article;
#Column(name = "ACCCOD_0")
private String acccod;
public String getCode_article() {
return code_article;
}
public void setCode_article(String code) {
this.code_article = code;
}
public String getAcccod() {
return acccod;
}
public void setAcccod(String acccod) {
this.acccod = acccod;
}
}
I want to make the schema attribute to be dynamic depending on a properties file property value , for example : env.schema = TOMEXPL.
How to achieve that ?

I didn't tried it but I guess this should work.
public class DynamicNamingStrategy extends DefaultNamingStrategy {
#Value("db.table.name")
private String name;
#Override
public String tableName(String tableName) {
return name;
}
...
}
SessionFactory sessionFactory;
Configuration config = new AnnotationConfiguration()
.configure("hibernate.cfg.xml")
.setNamingStrategy( new DynamicNamingStrategy() );
sessionFactory = config.buildSessionFactory();
session = sessionFactory.openSession();

Related

Problem when attempting a saveAndFlush commit (JPA ) when primary key is auto-generated from postGres trigger

I am using spring JPA to attempt to write records to a postGres DB. At the time of the commit, I am getting the following error:
Caused by: org.postgresql.util.PSQLException: ERROR: null value in column "col_id" violates not-null constraint
Detail: Failing row contains (null, null, null, null, null)
I have the following repository interface:
public interface MyRepo extends JpaRepository <MyModel, String> {
}
, the following model class:
#Entity
#Validated
#Table(name = "my_table", schema="common")
public class MyModel {
#Id
#Column(name = "col_id")
private String id;
#Column(name = "second_col")
private String secCol;
#Column(name = "third_col")
private String thirdCol;
#Column(name = "fourth_col")
private String fourthCol;
#Column(name = "fifth_col")
private String fifthCol;
public MyModel() {
}
public MyModel(String id, String secCol, String thirdCol, String fourthCol, String fifthCol) {
this.id = id;
this.secCol = secCol;
this.thirdCol = thirdCol;
this.fourthCol = fourthCol;
this.fifthCol = fifthCol;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSecCol() {
return secCol;
}
public void setSecCol(String secCol) {
this.secCol = secCol;
}
public String getThirdCol() {
return thirdCol;
}
public void setThirdCol(String thirdCol) {
this.thirdCol = thirdCol;
}
public String getFourthCol() {
return fourthCol;
}
public void setFourthCol(String fourthCol) {
this.fourthCol = fourthCol;
}
public String getFifthCol() {
return fifthCol;
}
public void setFifthCol(String fifthCol) {
this.fifthCol = fifthCol;
}
}
, and the relevant part of the service class:
public MyModel myModel (MyModel myModel) {
MyModel mm = null;
try {
mm = myRepo.saveAndFlush(myModel);
} catch ( Exception e) {
e.printStackTrace();
}
return mm;
}
UPDATE:
I finally realized that my problem is due to a database trigger that auto-generates primary key against a complex business rule. Also, I assume I might need to use a custom save method rather than the default repo.saveAndFlush? I would be grateful for any ideas given new information. Thanks!
I reproduced the exact same code in a test project with Postgres and it worked well for me. You are absolutely correct that the values of the model class are not populated. You must share your controller also. It may really help me to help you if I can get a look where your service is being called from. Only that will help me to deduce why your model values are being passed as null in the service call.

What is the ideal way to serialize and deserialize polymorphic entity attribute in spring boot?

I have an Entity class with a column attribute whose type is an abstract class. I want to serialize (object to JSON string) while saving it in the database column and deserialize it into an abstract class (which in turn converts the string to the appropriate concrete class) when it is retrieved from the database.
Here's how I accomplished it:
ProductEntity.java
#Entity
#Table(name="PRODUCT")
#Data
public class ProductEntity{
#Id
#Column(name = "ID", insertable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger id;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "NAME")
private String name;
#Column(name = "PRODUCT_TYPE")
private String productType;
#Column(name = "PRODUCT_SPECS")
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property =
"productType") // -------------------> Map to concrete class based on productType value
#Convert(converter = ObjectConverter.class) // ------------> custom converter
private ProductSpecification productSpec;
}
NOTE : "PRODUCT_SPECS" database column is of JSON type.
ProductSpecification.java
#NoArgsConstructor
#JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.WRAPPER_OBJECT,
#JsonSubTypes({
#JsonSubTypes.Type(value = ComputerSpecification.class, name = "computer"),
#JsonSubTypes.Type(value = SpeakerSpecification.class, name = "speaker")
})
public abstract class ProductSpecification{ }
ComputerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class ComputerSpecification extends ProductSpecification {
String memory;
String displaySize;
String processor;
#JsonCreator
public ComputerSpecification (#JsonProperty("memory") String memory,
#JsonProperty("displaysize") String displaySize,
#JsonProperty("processor") String processor){
super();
this.memory = memory;
this.displaySize = displaySize;
this.processor = processor;
}
}
SpeakerSpecification.java
#Getter
#Setter
#NoArgsConstructor
#JsonTypeName("computer")
public class SpeakerSpecification extends ProductSpecification {
String dimension;
String sensitivity;
String bassPrinciple;
String amplifierPower;
#JsonCreator
public SpeakerSpecification (#JsonProperty("sensitivity") String sensitivity,
#JsonProperty("dimension") String dimension,
#JsonProperty("bassPrinciple") String bassPrinciple,
#JsonProperty("amplifierPower") String amplifierPower){
super();
this.sensitivity = sensitivity;
this.dimension = dimension;
this.bassPrinciple = bassPrinciple;
this.amplifierPower = amplifierPower;
}
}
ObjectConverter.java
NOTE: I am using Jackson ObjectMapper for serialization and deserialization.
public class ObjectConverter implements AttributeConverter<Object, String>{
private final static Logger LOGGER = LoggerFactory.getLogger(ObjectConverter.class);
private static final ObjectMapper mapper;
static {
mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
}
#Override
public String convertToDatabaseColumn(Object attributeObject) {
if (attributeObject == null) {
return "";
}
try {
return mapper.writeValueAsString(attributeObject);
} catch (JsonProcessingException e) {
LOGGER.error("Could not convert to database column", e);
return null;
}
}
#Override
public Object convertToEntityAttribute(String dbColumnValue) {
try {
if (StringUtils.isBlank(dbColumnValue)) {
return null;
}
return mapper.readValue(dbColumnValue, ProductSpecification.class); // ----> mapped to
abstract class
} catch (Exception e) {
LOGGER.error("Could not convert to entity attribute", e);
return null;
}
}
}
Request body 1:
{
"name" : "Bose Bass Module 700 - Black- Wireless, Compact Subwoofer",
"description" : "This wireless, compact subwoofer is designed to be paired with the Bose sound
bar 700 to bring music, movies, and TV to life with Deep, dramatic bass. ",
"productSpec" : {
"sensitivity" : "90 dB",
"bassPrinciple" : "reflex",
"amplifierPower" : "700 watts",
"dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD"
}
}
This request gets saved in the database column "PRODUCT_SPECS" as :
{".SpeakerSpecification ":{"sensitivity" : "90 dB","bassPrinciple" : "reflex", "amplifierPower" :"700
watts", "dimension" : "14-5/16inW x 42-13/16inH x 16-5/16inD" }}
Now this solution works perfectly fine. The "SpeakerSpecification " key neither appears in the response of GET API call nor in the swagger doc. But having to store the type info in the database really bothers me.
Is there a better approach to this problem where I could avoid having the typeinfo (".SpeakerSpecification ") in the column value?

How do I insert values of elements that are part of the EmbeddedId in JPA?

I have a case where I need to execute an insert statement via createNativeQuery. I have an entity list I'm looping through in order to set the properties accordingly from another bean class, and then persist that data to the oracle database.
The problem I am facing is persisting the data that is part of the embeddedId (item, loc, weekstart, type, forecastId, insertTS). I need to persist that data for the new records to be inserted into the database. When I try to set the values from the POJO bean to my set method for the properties of my entity bean, nothing happens. Below is my code for setting the values of the properties from the POJO bean to my entity bean, along with my persistence method and the insert query being executed:
Validation class where validation occurs beforehand (missing to get the point) that includes the setting of my entity properties from the POJO bean:
List <InsertPromoData> insertPromos = new ArrayList<InsertPromoData>();
promo.forEach(record -> {
if (record.getErrorList().size() == 0) {
rowsSuccessful++;
Util.writeSuccessToFile(templateCd, successFile, record, successFields);
try {
InsertPromoData insertData = new InsertPromoData();
insertData.getId().setItem(record.getItem());
insertData.getId().setLoc(record.getLoc());
insertData.getId().setWeekStart(record.getWeek_Start_Date());
insertData.setNumberOfWeeks(record.getNumber_Of_Weeks());
insertData.getId().setType(record.getType());
insertData.getId().setForecastId(record.getForecast_ID());
insertData.setQty(record.getUnits());
insertPromos.add(insertData);
}
catch (Exception e) {
logger.error("Error with setting insertPromolist from promo list values and the error is " + e.getMessage());
}
}
else {
if (rowsFailure == 0) {
Util.writeHeaderToFile(templateCd, errorFile);
}
rowsFailure++;
Util.writeErrorToFile(templateCd, errorFile, record, record.getErrorList());
}
});
errorFile.close();
successFile.close();
OracleImpl.insertPromoData(insertPromos);
POJO bean (promo is the variable representing this list of beans in validation class above):
public class PromoBean extends ErrorListBean
{
public String Item;
public String Loc;
public String Week_Start_Date;
public String Units;
public String Forecast_ID;
public String Type;
public String Number_Of_Weeks;
public String getItem() {
return Item;
}
public void setItem(String item) {
Item = item;
}
public String getLoc() {
return Loc;
}
public void setLoc(String loc) {
Loc = loc;
}
public String getWeek_Start_Date() {
return Week_Start_Date;
}
public void setWeek_Start_Date(String week_Start_Date) {
Week_Start_Date = week_Start_Date;
}
public String getNumber_Of_Weeks() {
return Number_Of_Weeks;
}
public void setNumber_Of_Weeks(String number_Of_Weeks) {
Number_Of_Weeks = number_Of_Weeks;
}
public String getType() {
return Type;
}
public void setType(String type) {
Type = type;
}
public String getForecast_ID() {
return Forecast_ID;
}
public void setForecast_ID(String forecast_ID) {
Forecast_ID = forecast_ID;
}
public String getUnits() {
return Units;
}
public void setUnits(String units) {
Units = units;
}
}
Embeddable class representing the composite primary key of the table:
#Embeddable
public class PromoID implements Serializable {
#Column(name = "ITEM")
private String item;
#Column(name = "LOC")
private String loc;
#Column(name = "WK_START")
private String weekStart;
#Column(name = "TYPE")
private String type;
#Column(name = "FCSTID")
private String forecastId;
#Column(name = "U_TIMESTAMP")
private String insertTS;
public PromoID() {
}
public PromoID (String item, String loc, String weekStart, String type, String forecastId, String insertTS) {
this.item = item;
this.loc = loc;
this.weekStart = weekStart;
this.type = type;
this.forecastId = forecastId;
this.insertTS = insertTS;
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
public String getWeekStart() {
return weekStart;
}
public void setWeekStart(String weekStart) {
this.weekStart = weekStart;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getForecastId() {
return forecastId;
}
public void setForecastId(String forecastId) {
this.forecastId = forecastId;
}
public String getInsertTS() {
return insertTS;
}
public void setInsertTS(String insertTS) {
this.insertTS = insertTS;
}
//hashcode and equals methods
Persistence Bean:
#Entity
#Table(name = "U_USER_PROMO")
public class InsertPromoData {
#EmbeddedId
private PromoID id;
#Column(name="NUMBER_OF_WEEKS")
String numberOfWeeks;
#Column(name="QTY")
String qty;
#Id
#AttributeOverrides(
{
#AttributeOverride(name = "item",column = #Column(name="ITEM")),
#AttributeOverride(name = "loc", column = #Column(name="LOC")),
#AttributeOverride(name = "weekStart", column = #Column(name="WK_START")),
#AttributeOverride(name = "type", column = #Column(name="TYPE")),
#AttributeOverride(name = "forecastId", column = #Column(name="FCSTID"))
}
)
public PromoID getId() {
return id;
}
public void setId(PromoID id) {
this.id = id;
}
public String getNumberOfWeeks() {
return numberOfWeeks;
}
public void setNumberOfWeeks(String numberOfWeeks) {
this.numberOfWeeks = numberOfWeeks;
}
public String getQty() {
return qty;
}
public void setQty(String qty) {
this.qty = qty;
}
}
DAO class method to execute the update (entitymanagerfactory emf already initialized):
public static void insertPromoData(List<InsertPromoData> insertData) {
logger.debug("Execution of method insertPromoData in Dao started");
System.out.println("Size of the insertData list is " + insertData.size());
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
System.out.println("Beginning transaction for insertPromoData");
Query query = em.createNativeQuery(env.getProperty("insertPromoUploadData"));
for (InsertPromoData promoData : insertData) {
query.setParameter("item", promoData.getId().getItem());
query.setParameter("location", promoData.getId().getLoc());
query.setParameter("wkStart", promoData.getId().getWeekStart());
query.setParameter("numberOfWeeks", promoData.getNumberOfWeeks());
query.setParameter("type", promoData.getId().getType());
query.setParameter("fcstId", promoData.getId().getForecastId());
query.setParameter("quantity", promoData.getQty());
query.executeUpdate();
}
em.getTransaction().commit();
}
catch(Exception e) {
logger.error("Exception in beginning transaction");
e.printStackTrace();
}
finally {
em.clear();
em.close();
}
logger.debug("Execution of method insertPromoData in Dao ended");
}
Query in properties file:
insertPromoUploadData = INSERT INTO {h-schema}U_USER_PROMO (ITEM, LOC, WK_START, NUMBER_OF_WEEKS, TYPE, FCSTID, QTY, U_TIMESTAMP) VALUES (:item, :location, TO_DATE(:wkStart,'MM DD YYYY'), :numberOfWeeks, :type, :fcstId, :quantity, SYSDATE)
My list size from my DAO class is returning as 0 once I begin the transaction and not sure why it is empty. Is there a reason that it is empty? I'm trying to persist each of the fields to the database (including the composite key fields) via insert query. Any help appreciated.
After looking into this for hours, I finally came to the conclusion that the simplest way to executeUpdate() without running into issues due to my current #EmbeddedId/#Embeddable logic was to change it to use #IdClass for my composite PK class, and annotate the fields from the PK in my entity with #Id. This allowed my data to be persisted to the database. Another slight difference was adding the insertTS in my entity class and annotating with #Id and generating getters/setters. This was necessary for JPA to recognize all the properties being referenced that I am wanting to persist, though I am persisting insertTS using SYSDATE function from the oracle DB instead of utilizing the get/set methods and setting to the current time from the java side.
I am sure there is a way to use #EmbeddedId/#Embeddable logic and be able to persist the fields that are part of the EmbeddedId, however, this I found to be a more simplistic way of doing it without further complexity in the code.

How does Spring's JPARepository and #Transactional behave together?

I have two methods (in a Spring boot application) that handle an entity. The entity has two fields, both boolean isDefault and isPdfGenerated. The first method (which is called from a controller) changes the isDefault flag when a new entity is created while the second one (called from a #Scheduled annotated method) changes the isPdfGenrated after it generates a pdf file for that entity.
My problem is that sometimes the second method finds entities with the isPdfGenerated flag set to false even though the file has been generated and saved in the database.
Both the methods have the #Transactional annotation and the repository interface for the entity extends JpARepository.
My guess is that the first method loads the entity from the database before the second method does but saves the entity after the second method does its job, thus overriding the isPdfGenerated flag.
Is this possible ? If the answer is yes, how should one handle such cases ? Shouldn't JPARepository handle the case when an entity gets updated from an external source ?
Bellow is some code to better illustrate the situation.
MyController:
#Controller
#RequestMapping("/customers")
public class MyController {
#Autowired
private EntityService entityService;
#RequestMapping(value = "/{id}/changeDefault", method = RequestMethod.POST)
public String changeDefault(#PathVariable("id") Long customerId, #ModelAttribute EntityForm entityForm, Model model) {
Entity newDefaultEntity = entityService.updateDefaultEntity(customerId, entityForm);
if (newDefaultEntity == null)
return "redirect:/customers/" + customerId;
return "redirect:/customers/" + customerId + "/entity/default;
}
}
EntityService:
import org.springframework.transaction.annotation.Transactional;
#Service
public class EntityService {
#Autowired
private EntityRepository entityRepository;
#Autowired
private CustomerRepository customerRepository;
#Transactional
public Entity updateDefaultEntity(Long customerId, submittedData) {
Customer customer = customerRepository.findById(customerId);
if(customer == null)
return customer; // I know there are better ways to do this
Entity currentDefaultEntity = entityRepository.findUniqueByCustomerAndDefaultFlag(customer, true);
if(currentDefaultEntity == null)
return null; // I know there are better ways to do this also
Entity newDefaultEntity = new Entity();
newDefaultEntity.setField1(submittedData.getField1());
newDefaultEntity.setField2(submittedData.getField2());
newDefaultEntity.setCustomer(customer);
oldDefaultEntity.setDefaultFlag(false);
newDefaultEntity.setDefaultFlag(true);
entityRepository.save(newDefaultEntity);
}
#Transactional
public void generatePdfDocument(Entity entity) {
Document pdfDocument = generateDocument(entity);
if(pdfDocument == null)
return;
documentRepository.save(pdfDocument);
entity.setPdfGeneratedFlag(true);
entityRepository.save(entity);
}
}
ScheduledTasks:
#Component
public class ScheduledTasks {
private static final int SECOND_IN_MILLISECONDS = 1000;
private static final int MINUTE_IN_SECONDS = 60;
#Autowired
private EntityRepository entityRepository;
#Autowired
private DocumentService documentService;
#Scheduled(fixedDelay = 20 * SECOND_IN_MILLISECONDS)
#Transactional
public void generateDocuments() {
List<Quotation> quotationList = entityRepository.findByPdfGeneratedFlag(false);
for(Entity entity : entitiesList) {
documentService.generatePdfDocument(entity);
}
}
}
DocumentService:
#Service
public class DocumentService {
#Autowired
private EntityRepository entityRepository;
#Autowired
private DocumentRepository documentRepository;
#Transactional
public void generatePdfDocument(Entity entity) {
Document pdfDocument = generateDocument(entity);
if(pdfDocument == null)
return;
documentRepository.save(pdfDocument);
entity.setPdfGeneratedFlag(true);
entityRepository.save(entity);
}
}
EntityRepository:
#Repository
public interface EntityRepository extends JpaRepository<Entity, Long> {
Entity findById(#Param("id") Long id);
List<Entity> findByPdfGeneratedFlag(#Param("is_pdf_generated") Boolean pdfGeneratedFlag);
Entity findUniqueByCustomerAndDefaultFlag(
#Param("customer") Customer customer,
#Param("defaultFlag") Boolean defaultFlag
);
}
DocumentRepository:
#Repository
public interface DocumentRepository extends JpaRepository<Document, Long> {
Document findById(#Param("id") Long id);
}
Entity:
#Entity
#Table(name = "entities")
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
public class Entity {
private Long id;
private boolean defaultFlag;
private boolean pdfGeneratedFlag;
private String field1;
private String field2;
private Customer customer;
public Entity() { }
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "is_default")
public boolean isDefaultFlag() {
return defaultFlag;
}
public void setDefaultFlag(boolean defaultFlag) {
this.defaultFlag = defaultFlag;
}
#Column(name = "is_pdf_generated")
public boolean isPdfGeneratedFlag() {
return pdfGeneratedFlag;
}
public void setPdfGeneratedFlag(boolean pdfGeneratedFlag) {
this.pdfGeneratedFlag = pdfGeneratedFlag;
}
#Column(name = "field_1")
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
#Column(name = "field_2")
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
#ManyToOne
#JoinColumn(name = "customer_id", referencedColumnName = "id", nullable = false)
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entity quotation = (Entity) o;
return id != null ? id.equals(entity.id) : entity.id == null;
}
#Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
#Override
public String toString() {
return "Entity{" +
"id=" + id +
", pdfGeneratedFlag=" + pdfGeneratedFlag +
", defaultFlag=" + defaultFlag +
", field1=" + field1 +
", field2=" + field2 +
", customer=" + (customer == null ? null : customer.getId()) +
"}";
}
}
I have omitted the other classes because they are either POJOs ( EntityForm ) or the same as other domain model classes ( Document ).
If you're talking about a row on the database that is getting updated by another process after the first process has read it but before it has been updated, then you need to put in some sort of optimistic locking strategy.
This will be handled by the underlying ORM api (e.g. Hibernate or Eclipselink) rather than Spring Data (which will just handle an optimistic locking errors thrown by the ORM).
Have a look at this article. Bear in mind that if you want optimistic locking you need some way of determining a row's version. In JPA this is normally done using a column annotated with the #Version tag.
https://vladmihalcea.com/hibernate-locking-patterns-how-does-optimistic-lock-mode-work/

Problems with WebDataBinder and Set.Class

i am having trouble with binding my data from a form :
I have two class
#Entity
#Table(name = "ROLES")
public class Role implements GenericDomain {
private Long id;
private String code;
private String name;
private Set<Privilege> privileges = new HashSet<Privilege>(0);
public Role() {}
/* getter and setter*/
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "ROLES_PRIVILEGES"
, joinColumns = { #JoinColumn(name = "ROLE_ID") }
, inverseJoinColumns = { #JoinColumn(name = "PRIVILEGE_ID") }
)
public Set<Privilege> getPrivileges() {
return this.privileges;
}
public void setPrivileges(Set<Privilege> privileges) {
this.privileges = privileges;
}
/* overide of hascode, equals*/
}
And
#Entity
#Table(name = "PRIVILEGES")
public class Privilege implements GenericDomain {
private Long id;
private String code;
private Set<Role> roles = new HashSet<Role>(0);
public Privilege() {}
/* getter and setter*/
#ManyToMany(cascade=CascadeType.REFRESH, mappedBy="privileges")
public Set<Role> getRoles() {
return this.roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
#Override
public String toString(){
return this.getCode() + this.getComment();
}
/*overide equals and hascode*/
and in my controller i have :
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Set.class, "privileges", new CustomCollectionEditor(Set.class) {
#Override
protected Object convertElement(Object element) {
return (element == null)?null:privilegeService.getOne(Integer.parseInt((String)element));
}
});
}
#RequestMapping(value = "edit", method = RequestMethod.POST)
public String saveOldRole( #ModelAttribute("role") Role role
, BindingResult result
, ModelMap model
) {
validator.validate(role, result);
if (result.hasErrors()){
logger.error(result.getAllErrors());
model.addAllAttributes(result.getModel());
return "/admin/role/edit";
}
logger.info(role.getPrivileges());
Iterator p = role.getPrivileges().iterator();
while(p.hasNext()){
logger.info(p.next().getClass());
}
roleService.saveOrUpdate(role);
model.addAttribute("roles", roleService.getAll());
sessionStatus.setComplete();
return "redirect:/admin/role/list.do";
}
and my debug is
role.RoleController:93 - [[MANAGE_USERS], [MANAGE_ROLES]]
role.RoleController:96 - class java.util.LinkedHashSet
role.RoleController:96 - class java.util.LinkedHashSet
22:29:44,915 ERROR tomcat-http--7 property.BasicPropertyAccessor:194 - IllegalArgumentException in class: com.stunaz.domain.Privilege, getter method of property: id
I dont understand why at 96, the class type is java.util.LinkedHashSet, i thought it should be Privileges.
I dont understand why my role.getPrivileges() is a Set of Set, it should be a Set of Privilege.
Of course at saveOrUpdate am getting an error.
finaly!!!
there were no bug at all!
i updated my spring jar from 3.0.5.RELEASE to 3.1.0.M1, and voila : somthing stopped working with webdatabinder and CustomCollectionEditor.
i just rollback to 3.0.5.RELEASE and everything is fine.

Resources