I am using spring data neo4j 6.1.3 and following is my use case code snippets
Domain Entity
#Data
#Node("DATspace")
public class DatSpace {
#Id #GeneratedValue
private Long neoId;
#Property("SUPtitle")
private String title;
private String SUPid;
}
Test class
#SpringBootTest
#EnableNeo4jRepositories(basePackages = "com.rahal.marvel")
public class ProjectionTest {
#Autowired
private Neo4jTemplate neo4jTemplate;
interface DATspaceProjection {
String getTitle();
String getSUPid();
}
#Test
public void test_projection(){
DatSpace d = neo4jTemplate.findOne("MATCH (s:DATspace {SUPid: $id}) RETURN s", Collections.singletonMap("id", "SPC_ML7"), DatSpace.class).get();
d.setTitle("title modified");
d.setSUPid("SUPid modified");
DATspaceProjection p = neo4jTemplate.saveAs(d, DATspaceProjection.class);
}
}
Ideally above saveAs function should modify both DATspace.SUPtitle and DATspace.SUPid. However it only modify SUPid but not SUPtitle. I presume it is due to property mapping (#Property) . Is this a bug or are there any workaround?
The provided #Property annotation does only have an impact on the annotated property (title) itself.
There is no knowledge right now that goes from the getTitle() method in the projection to the annotated title field in the domain class.
To be safe when modifying this use the explicit property name:
interface DATspaceProjection {
String getSUPtitle();
String getSUPid();
}
I created an issue for improvement https://github.com/spring-projects/spring-data-neo4j/issues/2371
Related
I am working spring boot project that uses spring data as an abstraction to access the database[MongoDB]. I want to change the write concern only for two specific operations.
Below is the entity and repository class that I use to access the Mongo DB collection:
Entity
#Document(collection = "tests")
#Data
#NoArgsConstructor
public class Test {
#Id
private String id;
private String name;
private String category;
}
Repository
#Repository
public interface TestRepository extends BaseMongoRepository<Test> {
...
#DeleteQuery(value="{'id':?0}, { writeConcern: { w : '2', wtimeout : 1000 }, delete=true")
void safeDeleteByTestId(String id,String name);
default void updateNameForAll(String category) {
final Query query = query(where("category").is(category);
final Update update = Update.update("name", name);
getMongoOperations().updateMulti(query, updategetMetadata().getCollectionName());
}
...
}
How can I modify the updateNameForAll method to increase the write concern only for this query? I don't want to override write concern for the entire collection or database.
Kind Regards,
Rando.
I found a workaround to this issue:
I created a new interface named ETestRepository like below:
public interface ETestRepository {
void safeUpdateNameForAll(String category);
}
Then I created a implementation of the interface:
public interface ETestRepositoryImpl implements ETestRepository {
#Autowired
private MongoTemplate mongoTemplate;
#Override
public void safeUpdateNameForAll(String accountId, String contextId, ChangeSetRowAction action) {
mongoTemplate.setWriteConcern(WriteConcern.W2);
final Query query = query(where("category").is(category);
final Update update = Update.update("name", name);
mongoTemplate.updateMulti(query,update, Test.class);
}
}
In the end, I modified the TestRepository interface to extend the ETestRepository interface to include the safeUpdateNameForAll method.
I'm creating DTO versions of all my entities. I have a problem with an entity that has one Enum value. This is my entity:
#Getter
#Setter
#Table(name = "TIPOS_MOVIMIENTO")
#Entity
public class TipoMovimiento {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
#Convert(converter = TipoMovEnumConverter.class)
private TipoMov tipo;
public String getTipo() {
return tipo.getTipoNombre();
}
#OneToMany(mappedBy = "tipoMov")
private List<Movimiento> movimientos;
No, I don't have #Enumerated because I followed a tutorial: "JPA 2.1 Attribute Converter – The better way to persist enums" and I had to remove it. I use a converter, as you can see.
This is my DTO of the previous entity:
#Getter
public class TipoMovimientoDto implements DtoEntity {
#Convert(converter = TipoMovEnumConverter.class) //I don't even know if write this here!!!!!
private TipoMov tipo;
}
The reason why I've followed that tutorial ↑ is because I wanted to write in database the variable values (tipoNombre) of enum (not enum name itself) because format. I want to store it in DB with accents, and I want to show it in Postman or whatever REST Client app with accents! Don't tell me anything about format it in front-end because this project is only back-end :(
Well, I think you will understand what I found with this with a image:
If you know a better way to do this, let me know, but this is not my problem now.
Let me show you the Enum:
public enum TipoMov {
INGRESO("Ingreso"),
PRESTAMO("Prestamo"),
PAGO("Pago"),
AMORTIZACION("Amortización"),
INTERES("Interés");
private String tipoNombre;
public String getTipoNombre() {
return tipoNombre;
}
TipoMov(String tipoNombre) {
this.tipoNombre = tipoNombre;
}
public static TipoMov fromDBName(String tipoNombre) {
switch (tipoNombre) {
case "Ingreso":
return TipoMov.INGRESO;
case "Préstamo":
return TipoMov.PRESTAMO;
case "Pago":
return TipoMov.PAGO;
case "Amortización":
return TipoMov.AMORTIZACION;
case "Interés":
return TipoMov.INTERES;
default:
throw new IllegalArgumentException("ShortName [" + tipoNombre
+ "] not supported.");
}
}
}
The problem is that I can't get the output in Postman if I convert this to DTO version. I get the appropiate output without DTO. I'm using REST services. Let me show you the services and controller.
(They include both versions, without DTO and with DTO (that is not working)).
ServiceImpl
#Service
public class TipoMovimientoServiceImpl implements TipoMovimientoService {
#Autowired
TipoMovimientoRepository repository;
#Autowired
DtoUtils dtoUtils;
public List<DtoEntity> findAllDto() {
List<TipoMovimiento> tiposMovimiento = repository.findAll();
List<DtoEntity> tiposMovimientoDto = new ArrayList();
for (TipoMovimiento tipoMovimiento : tiposMovimiento) {
DtoEntity tipoMovimientoDto= dtoUtils.convertToDto(tipoMovimiento, new TipoMovimientoDto());
tiposMovimientoDto.add(tipoMovimientoDto);
}
return tiposMovimientoDto;
}
public List<TipoMovimiento> findAll() {
List<TipoMovimiento> tiposMovimiento = repository.findAll();
return tiposMovimiento;
}
}
Service Interface
public interface TipoMovimientoService {
List<DtoEntity> findAllDto();
List<TipoMovimiento> findAll();
}
Controller:
#RestController
public class PruebasController {
#Autowired
TipoMovimientoService service;
#GetMapping("tiposmovdto")
public ResponseEntity <List <DtoEntity> > findAllDto() {
return ResponseEntity.ok(service.findAllDto());
}
#GetMapping("tiposmov")
public ResponseEntity <List <TipoMovimiento> > findAll() {
return ResponseEntity.ok(service.findAll());
}
}
As I said, the nonDto version works perfectly, but DTO version no. Is not the fault of DTO converter, because I have other REST services (that don't have enums) working perfectly with DTO. This is a problem about making compatible Enum and Dto!
I got it!!! I never thought this would work.
#Getter
public class TipoMovimientoDto implements DtoEntity {
private TipoMov tipo;
}
I just changed in the code above (Dto):
private TipoMov tipo;
to
private String tipo;
I can't explain how Enum from Entity could have been converted to DTO, using String instead Enum... But that worked!
In case you have the same problem... this is my Attribute Converter between Enum and String
#Converter(autoApply = true)
public class TipoMovEnumConverter implements AttributeConverter<TipoMov, String> {
public String convertToDatabaseColumn(TipoMov tipoMov) {
return tipoMov.getTipoNombre();
}
public TipoMov convertToEntityAttribute(String dbData) {
return dbData == null ? null : TipoMov.fromDBName(dbData);
}
}
Is still necessary to use it in Entity class, above of the enum variable:
#Convert(converter = TipoMovEnumConverter.class)
But not necessary in DTO. Just use String instead Enum in DTO!
In my current project, we will refractor the nls-support for database entities. In our previous version, we used the session language, but unfortualy this behaviour was not completely stable. So we will change the code, so that the language information is stored inside the query.
I would love to have a central instance to handle this language behaviour instead of changing each query, in every entity spread over the whole project.
E.g. I have this entity:
#NamedQueries({
#NamedQuery(name = NLSBackendEntity.findAll,
query = "select n from NLSBackendEntity n"),
#NamedQuery(name = NLSBackendEntity.getById,
query = "select n from NLSBackendEntity n where n.nlsBackendKey.key = :key") })
#Entity
#Table(name = "backend_key_al")
public class NLSBackendEntity implements Serializable
{
private static final long serialVersionUID = 1L;
public static final String findAll = "NLSBackend.findAll";
public static final String getById = "NLSBackend.getById";
#EmbeddedId
private NLSBackendKey nlsBackendKey;
/**
* The text in the language.
*/
#Lob
#Column(name = "TEXT")
private String text;
NLSBackendEntity()
{
// no arg constructor needed for JPA
}
public String getKey()
{
return nlsBackendKey.key;
}
public String getLanguage()
{
return nlsBackendKey.language;
}
public String getText()
{
return text;
}
#Embeddable
public static class NLSBackendKey implements Serializable
{
private static final long serialVersionUID = 1L;
/**
* the NLS-key.
*/
#Column(name = "KEY")
private String key;
/**
* The language of this entry.
*/
#Column(name = "LOCALE")
private String language;
}
}
One possibility would now be to add the n.nlsBackenKey.language = :locale to every NamedQuery and to change every call, where this NamedQuery is referenced.
A more favorable way would be, to have a Customizer to add the locale paramter. Atm I have this:
public class QueryLanguageCustomizer implements DescriptorCustomizer
{
#Override
public void customize(ClassDescriptor descriptor) throws Exception
{
ExpressionBuilder eb = new ExpressionBuilder(descriptor.getJavaClass());
Expression languageExp = eb.getField("LOCALE").equal(eb.getParameter("locale"));
descriptor.getQueryManager().setAdditionalJoinExpression(languageExp);
}
}
And I added this to the NLSBackendEntity: #Customizer(QueryLanguageCustomizer.class)
But now, I'm not able to set this parameter. Again, my favored way, would be to use a SessionEventAdapter:
public class LanguageSessionEventListener extends SessionEventAdapter {
/** Log for logging. */
private static final Log LOG = LogFactory
.getLog(LanguageSessionEventListener.class);
#Override
public void preExecuteQuery(SessionEvent event) {
LOG.debug("preExecuteQuery called for session= "
+ event.getSession().getName());
event.getQuery().getTranslationRow().put("LOCALE", getLocale());
super.preExecuteQuery(event);
}
private String getLocale() {
// uninteresting for this example
}
}
Unfortunatly no matter what I try, I'm unable to set this parameter. The getTransaltionRow() returns null, and every other possibility I tried failed.
Are there no possibilitys to set this parameter inside the preExecuteQuery block?
I'm using Eclipselink 2.5, any help is highly appreciated
If you don't mind using vendor-specific solution you could use EclipseLink #AdditionalCriteria annotation. You could use it as follows:
Create an abstract mapped class and derive all entities from it:
#MappedSuperclass
#AdditionalCriteria("this.language = :lang")
public class AbstractEntity {
private String language;
// getters + setters
}
Let your entities subclass it:
public class NLSBackendEntity extends AbstractEntity implements Serializable {
// ...
}
Set the value of language property on either the entity manager or entity manager factory:
entityManager.setProperty("language", "de");
before the queries are executed. EclipseLink should append language = ? to the where condition of your query binding the value you set on the entity manager.
You can use ThreadLocal<String> threadProps = new ThreadLocal<String>(); which you can set it on data or rest factory class and use it in your code.
The above solution will work if you not creating any new additional thread inside your functions.
Instead of parameter you can use threadProps.get();
I have a controller which produces JSON, and from this controller, I return an entity object, which is automatically serialized by Jackson.
Now, I want to avoid returning some fields based on a parameter passed to the controller. I looked at examples where this is done using FilterProperties / Mixins etc. But all the examples I saw requires me to use ObjectMapper to serialize / de-serialize the bean manually. Is there any way to do this without manual serialization? The code I have is similar to this:
#RestController
#RequestMapping(value = "/myapi", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(value = "/test/{variable}",method=RequestMethod.GET)
public MyEntity getMyEntity(#PathVariable("variable") String variable){
return myservice.getEntity(variable);
}
}
#Service("myservice")
public class MyService {
#Autowired
private MyEntityRepository myEntityRepository;
public MyEntity getEntity(String variable){
return myEntityRepository.findOne(1L);
}
}
#Entity
#Table(name="my_table")
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyEntity implements Serializable {
#Column(name="col_1")
#JsonProperty("col_1")
private String col1;
#Column(name="col_2")
#JsonProperty("col_2")
private String col2;
// getter and setters
}
Now, based on the value of "variable" passed to the controller, I want to show/hide col2 of MyEntity. And I do not want to serialize/deserialize the class manually. Is there any way to do this? Can I externally change the Mapper Jackson uses to serialize the class based on the value of "variable"?
Use JsonView in conjunction with MappingJacksonValue.
Consider following example:
class Person {
public static class Full {
}
public static class OnlyName {
}
#JsonView({OnlyName.class, Full.class})
private String name;
#JsonView(Full.class)
private int age;
// constructor, getters ...
}
and then in Spring MVC controller:
#RequestMapping("/")
MappingJacksonValue person(#RequestParam String view) {
MappingJacksonValue value = new MappingJacksonValue(new Person("John Doe", 44));
value.setSerializationView("onlyName".equals(view) ? Person.OnlyName.class : Person.Full.class);
return value;
}
Use this annotation and set the value to null, it will not be serialised:
#JsonInclude(Include.NON_NULL)
I'm using neo4j + spring data. To access the data I'm using interfaces, that extends GraphRepository<E>. For example
public interface EntryRepository extends GraphRepository<Entry> {
#Query("start parent=node({0}), entry=node({1}) "
+ "match parent-[*1..2{removed:false}]->entry "
+ "return distinct entry")
Entry findOne(Long parentId, Long entryId);
}
I'm trying to get data, that differs from my domain models. My custom models looks like that
#QueryResult
public class EntryBean {
#ResultColumn("id")
private Long id;
#ResultColumn("name")
private String name;
#ResultColumn("content")
private String content;
...
//getters and setters
}
#QueryResult
public class BoardBean {
#ResultColumn("id")
private Long id;
#ResultColumn("name")
private String name;
...
//getters and setters
}
Obviously, that it will be better to separate duplicate fields to Base class and inherit from it. So, i'm doing next steps
#QueryResult
public class BaseBean {
#ResultColumn("id")
private Long id;
#ResultColumn("name")
private String name;
...
}
#QueryResult
public class EntryBean extends BaseBean{
#ResultColumn("content")
private String content;
...
//getters and setters
}
And I don't need BoardBean anymore. But when I'm trying run query
public interface EntryRepository extends GraphRepository<Entry> {
#Query("start user=node({0}), board=node({1}) "
+ "... "
+ "return id(entry) as id, entry.name as name, entry.content as content")
List<EntryBean> getRelatedEntries(Long userId, Long boardId);
}
I get filled by data just fields that directly declared into EntryBean class (i. e. "content" field).
So, How I can correctly implement the #QueryResult class hierarcy?
This is a bug which has been present for almost two years (even in 2.3.5.RELEASE!) in the class in charge of converting annotated POJOs.
Indeed, it calls getDeclaredFields on the most concrete type thus skipping possibly inherited annotated fields.
Before the issue is fixed, my piece of advice would be to tolerate this superficial field duplication on your side and not relying on inheritance for now.