I have a custom class with type parameters and it is not being invoked by constraint validator. Is there a way to invoke the validator for type parameters?
SpringController.java
#ResponseBody
#RequestMapping(method = RequestMethod.POST)
public EntityCollection<MyEntity> processData(
#Valid #RequestBody EntityCollection<MyEntity> entityRequestSet
, BindingResult bindingResult) throws Exception {
if(bindingResult.hasErrors())
{
// Problem here is that bindingResult has no errors, even though MyEntity
// has nulls in it. If I use just MyEntity as RequestBody instead of
// EntityCollection<MyEntity>, then the bindingResult has errors in it
// for fields with nulls
MethodParameter parameter = new MethodParameter(this.getClass()
.getMethod(new Object(){}.getClass().getEnclosingMethod().getName(),
EntityCollection.class, BindingResult.class), 0);
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
return null;
}
EntityCollection.java
public class EntityCollection<MyEntity> extends GenericCollectionEntity<MyEntity> {
public EntityCollection() {
super();
}
public EntityCollection(
Collection<MyEntity> entities) {
super(entities);
}
}
GenericCollectionEntity.java
public abstract class GenericCollectionEntity<T> implements Serializable {
private static final long serialVersionUID = 1L;
public GenericCollectionEntity() {
super();
}
public GenericCollectionEntity(Collection<T> entities) {
super();
this.entities = entities;
}
protected Collection<T> entities;
public Collection<T> getEntities() {
return entities;
}
public void setEntities(Collection<T> entities) {
this.entities = entities;
}
}
MyEntity.java
#Entity
public class MyEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Valid
#EmbeddedId
private EntityKey key;
// getters & setters
}
EntityKey.java
#Embeddable
public class EntityKey implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull
#Column(name = "ID")
private String id;
// ommitted other fields
//getters & setters
}
My bad, I couldn't apply my thinking on the basics. I just added #Valid to the field in abstract entity and it started validating the collection.
GenericCollectionEntity.java
#Valid
protected Collection<T> entities;
Related
findAll() of mongoRepository returns empty list. what is wrong with the below code?
API used for counting the number of documents in the collection works fine.
Controller
#RestController
#RequestMapping("/api/api-management/scopes")
public class AuthScopesController {
private final ScopesService scopesService;
#Autowired
AuthScopesController(ScopesService scopesService) {
this.scopesService = scopesService;
}
#PostMapping("/")
public AuthScope createScope(#RequestBody AuthScope authScope) {
return scopesService.createAuthScope(authScope);
}
#GetMapping("/")
public List<AuthScope> getAllScopes() {
return scopesService.getAuthScopes();
}
}
service
#Service
public class ScopesService {
private final AuthScopeRepository authScopeRepository;
public ScopesService(AuthScopeRepository authScopeRepository) {
this.authScopeRepository = authScopeRepository;
}
public AuthScope createAuthScope(AuthScope authScope) {
return authScopeRepository.save(authScope);
}
//TODO: recheck
public List<AuthScope> getAuthScopes() {
return authScopeRepository.findAll();
}
}
repository
#Repository
public interface AuthScopeRepository extends MongoRepository<AuthScope, String> {
Optional<AuthScope> findByScope(String id);
}
model is as follows
#Data
#Document("auth-scopes")
public class AuthScope {
#Id
private String scope;
private String belongsToApi;
private String belongsToApiTitle;
private String description;
}
found the issue. in order to findAll() to work, the model has to have deleted status.
I've updated the model as follows
#Data
#Document("auth-scopes")
public class AuthScope {
#Id
private String scope;
private String belongsToApi;
private String belongsToApiTitle;
private String description;
private boolean deleted;
}
I'm using Spring #Scope(value = "session", proxyMode=ScopedProxyMode.TARGET_CLASS) beans for objects that should be shared across a single Http-Session. This will provide for example one "Project" object for each User who is using my application.
To get this working I had to implement an interceptor for Hibernate that is returning the name of the class:
public class EntityProxySupportHibernateInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = 7470168733867103334L;
#Override
public String getEntityName(Object object) {
return AopUtils.getTargetClass(object).getName();
}
}
With this interceptor I can use a Spring CrudRepository to save a Project-entity in the database:
#Repository
public interface ProjectRepository extends CrudRepository<Project, Integer> {
Project findByProjectId(int projectId);
}
Project-entity:
#Component
#Entity
#Table(name = "xxx.projects")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class Project implements Serializable {
private static final long serialVersionUID = -8071542032564334337L;
private int projectId;
private int projectType;
#Id
#Column(name = "project_id")
public int getProjectId() {
return projectId;
}
public void setProjectId(int projectId) {
this.projectId = projectId;
}
#Column(name = "project_type")
public int getProjectType() {
return projectType;
}
public void setProjectType(int projectType) {
this.projectType = projectType;
}
}
Storing the Project in the database works as expected. I can have a look at the database and the correct values are inserted. Now I have a different entity that I'm creating the same way as the project and that I want to save in the database via a CrudRepository.
Here the problem begins. Hibernate is not inserting the values that I have set. Hibernate always only inserts null into the database. Reading the values in my Spring application is working as expected. I think that Hibernate is not using the proxy of the entity but the underlying blueprint of the object. How can I force Hibernate to use the proxy with the correct values?
Repository:
#Repository("DataInput001Repository")
public interface DataInputRepository extends CrudRepository<DataInput, DataInputId> {}
Entity:
#Component("DataInput001")
#Entity
#Table(name = "xx.data_input_001")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#IdClass(DatanputId.class)
public class DataInput implements Serializable {
private static final long serialVersionUID = -6941087210396795612L;
#Id
#Column(name = "project_id")
private int projectId;
#Column(name = "income")
private String income;
#Column(name = "income_increase")
private String incomeIncrease;
/* Getter + Setter */
}
Service:
#Service("DataInputService001")
public class DataInputServiceImpl implements DataInputService {
#Resource(name = "DataInputMapper001")
DataInputMapperImpl dataInputMapper;
#Resource(name = "DataInput001Repository")
DataInputRepository dataInputRepository;
#Resource(name = "DataInput001")
DataInput datanInput;
#Transactional
public void createDataInput(String json) throws Exception {
dataInputMapper.mapDataInput(json);
dataInputRepository.save(dataInput);
}
public DataInput getDataInput() {
return dataInput;
}
public void setDataInput(DataInput dataInput) {
this.dataInput = dataInput;
}
}
I'm writing an app using Spring Boot, Hiberane and Spring Data.
I have two tables in the db: tableA and tableB.
They have some common fields but their id's,name's are different, also I've created a basic model for them to contain some common fields, right now it looks something like this:
// BaseModel
#MappedSuperclass
public abstract class BaseModel implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name="common_field_1")
private String commonField1;
#Column(name="common_field_2")
private String commonField2;
#Column(name="common_field_3")
private String commonField3;
}
// ExactModel 1
#Entity
#Table(name="table1" ,schema="schema")
public class ExactModel1 extends BaseModel {
#Id
#Basic(fetch=FetchType.EAGER)
#Column(name="exact_model_id_1", nullable=false)
private long exactModel1Id;
private String exactField1;
}
// ExactModel 2
#Entity
#Table(name="table2" ,schema="schema")
public class ExactModel2 extends BaseModel {
#Id
#Basic(fetch=FetchType.EAGER)
#Column(name="exact_model_id_2", nullable=false)
private long exactModel2Id;
private String exactField2;
}
And I have some generic logic which implements some general crud logic which works for classes which extend BaseModel:
public abstract class BaseServiceImpl<M extends BaseModel, R extends BaseRepository<M>> implements BaseService<M, Long> {
private final R repository;
public BaseServiceImpl(R repository) {
this.repository = repository;
}
#Override
public M save(M model) {
return repository.save(model);
}
#Override
public List<M> saveAll(List<M> models) {
return repository.saveAll(models);
}
#Override
public M findById(Long id) {
return repository.getOne(id);
}
#Override
public List<M> findAllById(List<Long> ids) {
return repository.findAllById(ids);
}
#Override
public List<M> findAll() {
return repository.findAll();
}
#Override
public M update(M model) {
return repository.save(model);
}
#Override
public List<M> updateAll(List<M> models) {
return repository.saveAll(models);
}
#Override
public void delete(M model) {
repository.delete(model);
}
#Override
public void delteById(Long id) {
repository.deleteById(id);
}
#Override
public void deleteInBatch(List<M> models) {
repository.deleteInBatch(models);
}
#Override
public Long countModels() {
return repository.count();
}
}
The thing is now I need to get somehow the id of the entity I work with in this generic logic, but there is no id field in BaseModel, so I can't just use baseModel.getId().
The question: is it possible to define a mock id field in BaseModel and override it in the child classes, so I can use this id in the generic methods but Hibernate fills the actual ids on the runtime for me?
first, sorry about my bad english;
Second, I have the following Code:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class UserAccount implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private List<Venda> vendas;
}
And the following:
public class Venda implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UserAccount cliente;
}
So, everything is okay and get the json from serialize on this way (when I ask for an UserAccount):
[
{
"id": 1,
"vendas": [
{
"id": 1,
"cliente": 1,
}
]
}
]
And when I ask for a Venda:
[
{
"id": 1,
"cliente": {
"id": 1,
"vendas": [
{
"id": 1,
"cliente": 1
}
]
}
}
]
The problem is, I don't need the "cliente" information on "vendas" in the first case, but in the second one I need the "cliente" information, However I don't want his "vendas", cause I already got it before;
I already trid #JsonIgnore and didn't work for me, what should I do?
PS: I'm working with GSON to get the .Class from JSON, and I get a terrible Exception because sometimes cliente is an Object and sometimes is Integer, so if you guys have another solution that makes cliente and vendas don't change their type, i would to know too. :(
I was able to solve this using Jackson's Mix-in feature. The Mixin feature is a class were you can specify json annotations (on the class, fields and getters/setters) and they apply to the bean/pojo you serialize. Basically, a mixin allows adding annotations at run time and without chaning the bean/pojo source file. You use Jackson's module feature to apply a Mixin at run time.
So I created one mixin that dynamically adds #JsonIgnore annotation to vendas getter method of UserAccount class, and another mixin that adds #JsonIgnore annotation to cliente getter method of Venda class.
Here is the modified UserAccount class:
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class UserAccount implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private List<Venda> vendas = new ArrayList<>();
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public List<Venda> getVendas() { return vendas; }
public void setVendas(List<Venda> vendas) { this.vendas = vendas; }
public void addVenda(Venda v) {
this.vendas.add(v);
v.setCliente(this);
}
/**
* a Jackson module that is also a Jackson mixin
* it adds #JsonIgnore annotation to getVendas() method of UserAccount class
*/
public static class FilterVendas extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(UserAccount.class, FilterVendas.class);
}
// implementation of method is irrelevant.
// all we want is the annotation and method's signature
#JsonIgnore
public List<Venda> getVendas() { return null; }
}
Here is the modified Venda class:
public class Venda implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private UserAccount cliente;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public UserAccount getCliente() { return cliente; }
public void setCliente(UserAccount cliente) { this.cliente = cliente; }
/**
* a Jackson module that is also a Jackson mixin
* it adds #JsonIgnore annotation to getCliente() method of Venda class
*/
public static class FilterCliente extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Venda.class, FilterCliente.class);
}
// implementation of method is irrelevant.
// all we want is the annotation and method's signature
#JsonIgnore
public UserAccount getCliente() { return null; }
}
}
and the test method with run time object mapper configuration:
public static void main(String... args) {
Venda v = new Venda();
UserAccount ua = new UserAccount();
v.setId(1L);
ua.setId(1L);
ua.addVenda(v);
try {
ObjectMapper mapper = new ObjectMapper();
System.out.println("UserAccount: (unfiltered)");
System.out.println(mapper.writeValueAsString(ua));
mapper = new ObjectMapper();
// register module at run time to apply filter
mapper.registerModule(new Venda.FilterCliente());
System.out.println("UserAccount: (filtered)");
System.out.println(mapper.writeValueAsString(ua));
mapper = new ObjectMapper();
System.out.println("Venda: (unfiltered)");
System.out.println(mapper.writeValueAsString(v));
mapper = new ObjectMapper();
// register module at run time to apply filter
mapper.registerModule(new UserAccount.FilterVendas());
System.out.println("Venda: (filtered)");
System.out.println(mapper.writeValueAsString(ua));
} catch (Exception e) {
e.printStackTrace();
}
}
output:
UserAccount: (unfiltered)
{"id":1,"vendas":[{"id":1,"cliente":1}]}
UserAccount: (filtered)
{"id":1,"vendas":[{"id":1}]}
Venda: (unfiltered)
{"id":1,"cliente":{"id":1,"vendas":[{"id":1,"cliente":1}]}}
Venda: (filtered)
{"id":1}
Thanks guys, I got the solution by this way:
public class CustomClienteSerializer extends JsonSerializer<UserAccount> {
#Override
public void serialize(UserAccount cliente, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
cliente.setVendas(null);
generator.writeObject(cliente);
}
}
and adding this on my venda class:
#JsonSerialize(using = CustomClienteSerializer.class)
#ManyToOne(fetch = FetchType.EAGER)
private UserAccount cliente;
So... I got the json as I wanted!
I have a CrudRepository that is supposed to make a query with an array (findByIn). In my repository tests it works, but when I try to use the query in my service, it doesn't work. Could someone explain why it doesn't work? Here is my setup (excluding some code irrelevant to the question)
Database model:
#Entity
#Table(name="Place")
public class Place implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "placeId", nullable = false)
private Long placeId;
#Column(name = "owner", nullable = false)
private String owner;
public Long getPlaceId() {
return placeId;
}
public void setPlaceId(Long placeId) {
this.placeId = placeId;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
Repository:
#Repository
public interface PlaceRepository extends CrudRepository<Place, Long> {
List<Place> findByPlaceIdIn(Long[] placeId);
}
Service (this is the part not working):
#Service
public class PlaceService {
#Autowired
private PlaceRepository placeRepository;
public List<Place> getPlaces(Long[] placeIds) {
return placeRepository.findByPlaceIdIn(placeIds);
}
}
The problem is that in my service placeRepository.findByPlaceIdIn(placeIds) returns 0 objects if placeIds contains more than one item. If placeIds contains just one item, the query works fine. I tried replacing return placeRepository.findByPlaceIdIn(placeIds) with this piece of code that does the query for every array item one by one (this actually works, but I'd like to get the query work as it should):
ArrayList<Place> places = new ArrayList<Place>();
for (Long placeId : placeIds) {
Long[] id = {placeId};
places.addAll(placeRepository.findByPlaceIdIn(id));
}
return places;
I know that the repository should work, because I have a working test for it:
public class PlaceRepositoryTest {
#Autowired
private PlaceRepository repository;
private static Place place;
private static Place place2;
private static Place otherUsersPlace;
#Test
public void testPlacesfindByPlaceIdIn() {
place = new Place();
place.setOwner(USER_ID);
place2 = new Place();
place2.setOwner(USER_ID);
place = repository.save(place);
place2 = repository.save(place2);
Long[] ids = {place.getPlaceId(), place2.getPlaceId()};
assertEquals(repository.findByPlaceIdIn(ids).size(), 2);
}
}
I also have another repository for other model, which also uses findByIn and it works fine. I can't see any relevant difference between the repositories. I thought it might offer some more details to show the working repository, so I included it below:
Database model:
#Entity
#Table(name="LocalDatabaseRow")
#JsonIgnoreProperties(ignoreUnknown=false)
public class LocalDatabaseRow implements Serializable {
public LocalDatabaseRow() {}
public LocalDatabaseRow(RowType rowType) {
this.rowType = rowType;
}
public enum RowType {
TYPE1,
TYPE2
};
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
#JsonProperty("id")
private Long id;
#JsonProperty("rowType")
#Column(name = "rowType")
private RowType rowType;
public Long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public RowType getRowType() {
return rowType;
}
public void setRowType(RowType rowType) {
this.rowType = rowType;
}
}
Repository:
#Repository
public interface LocalDatabaseRowRepository extends CrudRepository<LocalDatabaseRow, Long> {
List<LocalDatabaseRow> findByRowTypeAndUserIdIn(RowType type, String[] userId);
}
try using a list instead :
findByPlaceIdIn(List placeIdList);
You have a typo in your code (the repository declaration in the service):
#Autowired
private placeRepository placeRepository;
Should be:
#Autowired
private PlaceRepository placeRepository;