Mongo #DBRef unique - spring

My application uses Spring Boot / JPA / MongoDB.
I map my domain classes to MongoDB using
org.springframework.data.mongodb.core.mapping.Document;
org.springframework.data.mongodb.core.index.Indexed;
org.springframework.data.mongodb.core.mapping.DBRef;
All is well except when trying to make a DBRef unique :
#DBRef #Indexed(unique = true)
private User owner;
I have tried different combinations of #DBRef, #Indexed (unique=true) and cannot make the DBRef unique. I can make other field-types unique, such as 'name' in the following example
#Indexed(unique = true)
#Size(min = 2, max = 100)
#Column(length = 100)
private String name;
but cannot find how to make my DBRef field unique.

I'm coming from the Morphia side of mapping, but I'd try this:
#CompoundIndexes({
#CompoundIndex(name = "owner", def = "{'owner.id' : 1}", unique = true)
})

Related

Spring Data + View with Union return duplicate rows

i'm using Spring Boot 2.4.2 and Data module for JPA implementation.
Now, i'm using an Oracle View, mapped by this JPA Entity:
#Entity
#Immutable
#Table(name = "ORDER_EXPORT_V")
#ToString
#Data
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class OrderExportView implements Serializable {
private static final long serialVersionUID = -4417678438840201704L;
#Id
#Column(name = "ID", nullable = false)
#EqualsAndHashCode.Include
private Long id;
....
The view uses an UNION which allows me to obtain two different attributes of the same parent entity, so for one same parent entity (A) with this UNION I get the attribute B in row 1 and attribute C in row 2: this means that the rows will be different from each other.
If I run the query with an Oracle client, I get the result set I expect: same parent entity with 2 different rows containing the different attributes.
Now the issue: when I run the query with Spring Data (JPA), I get the wrong result set: two lines but duplicate.
In debug, I check the query that perform Spring Data and it's correct; if I run the same query, the result set is correct, but from Java/Spring Data not. Why??
Thanks for your support!
I got it! I was wrong in the ID field.
The two rows have the same parent id, which is not good for JPA, which instead expects a unique value for each line.
So, now I introduced a UUID field into the view:
sys_guid() AS uuid
and in JPA Entity:
#Id
#Column(name = "UUID", nullable = false)
#EqualsAndHashCode.Include
private UUID uuid;
#Column(name = "ID")
private Long id;
and now everything works fine, as the new field has a unique value for each row.

Race Condition in Postgres SQL using Spring data JpaRepository

I am facing a wierd issue in my implementation where I am persisitng data to a PostgresSQL DB using Spring data JpaRepository
In my Entity class I have the below columns:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private int id;
#Column(name = "field1", nullable = false, length = 16)
private String field1;
#Column(name = "field2", nullable = false, length = 16)
private String field2;
#Column(name = "field3", nullable = false, length = 16)
private String field3;
I initially avoided declaring the fields above as composite since there were many fields to be dealt with as composite keys. I thought the java code check would do the trick in all scenarios
So basically, I have to maintain the uniqueness of each row based on field1,field2 and field3. That was the basic requirement for which I had checks in my java code that if any entry exists in the DB for the combination of field1,field2 and field3 then I used to throw java exceptions
No two rows can have these values repeating. All was good until the application was tested under some errorneous business scenarios which would never happen in production but got run by mistake
Whats happening now is that if 2 requests are triggered at the exact same instance with the exact same 3 fields above (through a script) then they both enter into the Database since both get the entry check as false
Would declaring all of them as one composite key resolve the situation?
You should define the unique constraint in your database in addition of JPA constraint.
#Entity
#Table(uniqueConstraints={
#UniqueConstraint(columnNames = {"field1", "field2", "field3"})
})
public class MyEntity {
...
}

Is there an elegant way to specify entity's fields to ignore by Spring Data Elasticsearch's ObjectMapper while left them being serialized for REST?

In other words, the common Jackson markup is not enough for serializing the same entity for using as the REST request response to the Angular frontend and to pass the object to Elasticsearch via the Jest client. Say, I have an image in the Entity as a byte array, and I'd like it to be stored to DB and be passed to the frontend, but don't like it being indexed by Elasticsearch to reduce the costs or quotas.
Now I have to use Jackson's JsonView to markup the fields to use for the Spring Data Elasticsearch's ObjectMapper:
#Entity
#Table(name = "good")
#org.springframework.data.elasticsearch.annotations.Document(indexName = "good", shards = 1, replicas = 0, refreshInterval = "-1")
public class Good implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#org.springframework.data.elasticsearch.annotations.Field(type = FieldType.Keyword)
#JsonView(Views.Elasticsearch.class)
private Long id;
#NotNull
#Column(name = "short_name", nullable = false)
#JsonView(Views.Elasticsearch.class)
#Field(store = true, index = true, type=FieldType.Text)
private String shortName;
#NotNull
#Column(name = "description", nullable = false)
#JsonView(Views.Elasticsearch.class)
#Field(store = true, index = true, type=FieldType.Text)
private String description;
#Lob
#Column(name = "image", nullable = false)
#JsonView(Views.Rest.class)
private byte[] image;
#Column(name = "image_content_type", nullable = false)
#JsonView(Views.Rest.class)
private String imageContentType;
#NotNull
#Column(name = "price", nullable = false)
#JsonView(Views.Elasticsearch.class)
#Field(store = true, index = true, type=FieldType.Integer)
private Integer price;
...
I have a clss for Views:
public class Views {
public static class Rest{}
public static class Elasticsearch{}
}
And the ObjectMapper set up in the corresponding Bean:
#Override
public String mapToString(Object object) throws IOException {
log.trace("Object to convert to JSON : {}",object);
log.trace("Converting to json for elasticsearch >>> {}",objectMapper.writer().withView(Views.Elasticsearch.class).writeValueAsString(object));
//log.trace("Converting to json for elasticsearch >>> {}",objectMapper.writeValueAsString(object));
return objectMapper.writerWithView(Views.Elasticsearch.class).writeValueAsString(object);
//return objectMapper.writeValueAsString(object);
}
So, I have to markup all the fields except the ignored to Elasticsearch with #JsonView(Views.Elasticsearch.class) and this is the error prone. Also, this field still requires #Field usage if I like to pass some parameters there like store or value type. When I have #JsonView(Views.Elasticsearch.class), but don't have #Field on some, the fields are created in the index on a fly, that allows them to search, but not in desired way.
The latest is the reason why if I just leave #Field there and don't place it over fields I don't want to index into Elasticsearch, the initial index indeed ignores them, but later requests pass the undesired field when the entity is serialized exactly the same way as it is done for the REST. And the index property is created on a fly, making resources being spent for the large binary object indexing. So it looks like #Field is used for the initial index creation on the startup, but are not configured to be used with ObjectMapper of the Spring Data Elasticsearch.
So, I'd like to make this ObjectMapper take only fields with #Field above them into account, i.e serialize the fields marked with #Field only and use no #JsonView staff. How can I configure it?
These are known problems when using the Jackson Object Mapper in Spring Data Elasticsearch (which als is the default) and this is one of the reasons, why in Spring Data Elasticsearch as of version 3.2 (which currently is in RC2 and will be available as 3.2.0.GA in mid-september), there is a different mapper available, the ElasticsearchEntityMapper.
This mapper still has to be setup explicitly, the reference documentation of 3.2.0.RC2 shows how to do this. Using this mapper the Jackson annotations do not influence the data stored in and read from Elasticsearch. And you can use the org.springframework.data.annotation.Transient annotation on a field to not have it stored in Elasticsearch.
The #Field annotation is used to setup the initial Elasticsearch mapping, properties not having this annotation are automatically mapped by Elasticsearch when they are inserted.

Is #ManyToOne's "optional" param automatically set using Kotlin's nullability

I read that specifying optional = false in the #ManyToOne association annotation could help Spring improve the performance of the queries.
In a Kotlin data class entity, do I actually need to specify the parameter in the annotation, or can Spring figure this out by itself using the nullability of the item field?
For instance, if I have the following declaration:
#Entity
#Table(name = ACCESS_LOGS_ARCHIVES_TABLE, indexes = [
Index(name = "access_logs_archives_item_idx", columnList = "access_item_id")
])
data class AccessLogArchive(
val date: LocalDate,
#ManyToOne(optional = false)
#JoinColumn(name = "access_item_id", nullable = false)
val item: AccessLogItem,
val occurrences: Int
) {
#Id
#GeneratedValue
var id: Long? = null
}
#Entity
#Table(name = ACCESS_ITEMS_TABLE)
data class AccessLogItem(
#Column(length = 3) val code: String,
#Column(columnDefinition = "text") val path: String,
#Column(length = 10) val verb: String
) {
#Id
#GeneratedValue
var id: Long? = null
}
In this case, I would for instance expect Spring to know that the item field is not nullable, and thus the relationship should be understood as optional=false even without specifying it as I did. Is this the case?
Same question goes for the #JoinColumn's nullable = false, by the way.
Consider a simple entity like a Room which has a #ManyToOne relationship to House.
#Entity
class Room(
#ManyToOne(optional = true)
val house: House
) {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
val id: Long = 0
}
JPA will create a room table with a column
`house_id` bigint(20) DEFAULT NULL
If you specify #ManyToOne(optional = false)
the column will look like this:
`house_id` bigint(20) NOT NULL
By specifiying optional you tell JPA how the schema should be generated, whether the column can be NULL or not.
At runtime trying to load a Room without a House will cause an Exception if the house property is not nullable (House instead of House?) even when value of optional is true.
The same applies to #JoinColumn.
Is #ManyToOne's “optional” param automatically set using Kotlin's
nullability?
No it is not. It is independent from that and by default set to true.
Conclusion: In order for you schema to reflect your entities it is a good idea to use optional = true if the house property would be nullable and optional = false if the house property would be non-nullable.

Spring JPA Repository generating incorrect SQL

I'm using Spring JPA Repository with Hibernate below towards a MySQL Database. On that environment, I've the following Entity:
#Entity
#Table(name="cod__postales")
public class CodigoPostal {
#Id
#GeneratedValue
private Long registro;
#Column(name = "`POST_Nº_CODIGO`", length = 6, nullable = false)
private String codigo = " ";
#Column(name = "POST_DESCRIP", length = 30, nullable = false)
private String descripcion = " ";
#Column(name = "POST_ZONA_ASIG", length = 2, nullable = false)
private String zona = " ";
Also I've the following Spring JPA Repository:
public interface CodigoPostalRepository extends JpaRepository<CodigoPostal, Long> {
CodigoPostal findOneByCodigo(String codigo);
}
As you can see, there isan special character on the first column name 'º'. The problem es that, when I call to repo.findOneByCodigo() method, the following SQL is generated
select codigopost0_.registro as registro1_2_,
codigopost0_.`post_nº_codigo` as post_nº_2_2_,
codigopost0_.post_descrip as post_des3_2_,
codigopost0_.post_zona_asig as post_zon4_2_
from cod__postales codigopost0_
where codigopost0_.`post_nº_codigo`=?
The problem is that sql gives an error because of the name spring/hibernate assing to that column: as post_nº_2_2_.
How can I avoid this?
This is a first thought regarding this query and my suggestion is to introduce a native query which will help you to change 'as post_nº_2_2_.' in something like: 'as post_n_2_2_.'.
Example:
#Query(value = " select codigopost0_.registro as registro1_2_,
codigopost0_.`post_nº_codigo` as post_n_2_2_,
codigopost0_.post_descrip as post_des3_2_,
codigopost0_.post_zona_asig as post_zon4_2_
from cod__postales codigopost0_
where codigopost0_.`post_nº_codigo`=?", nativeQuery = true)
public interface CodigoPostalRepository extends JpaRepository<CodigoPostal, Long> {
CodigoPostal findOneByCodigo(String codigo);
}
More information on this link.
At the end I've been able to solve this by adding the characterEncoding parameter to the url string:
spring.datasource.url=jdbc:mysql://localhost/madr?characterEncoding=UTF-8
Anyway, I've opened an issue to Hibernate because maybe this is not a valid option for everyone https://hibernate.atlassian.net/browse/HHH-10493
Change
#Column(name = "`POST_Nº_CODIGO`", length = 6, nullable = false)
to
#Column(name = "`POST_N_CODIGO`", length = 6, nullable = false)

Resources