How to change which field is used as _id without annotations in Spring data MongoDB - spring

I have some classes which are being generated from a WSDL using the CXF wsdl2java tool.
I would like to store instances of these classes in a MongoDB database, using Spring data MongoDB.
The default mapping is acceptable for this, except for one thing:
I would like to change which field is used as _id.
Normally this is done with a annotation like #Id.
But because these classes are generated, I would like to do this without an annotation.
Is there a (correct?) way to do this?
So my generated class is:
class Simple {
String businessId;
String otherfield1;
.
String otherfield999;
}
And I would like Spring data MongoDB to use 'businessId' as the '_id' field in MongoDB, without changing the 'Simple' class by adding an annotation.
Thanks!

That's currently not supported. The property either needs to be id, _id or annotated with #Id.

Related

How do I map "id" property into "id" field in Mongo without #Field annotation?

I have multiple classes with the id field. I would like to store their instances in MongoDB using spring-data-mongodb. I would like to map id property in these classes to id field in Mongo.
So here is what my classes look like:
public class Entity {
private final String id; // = 42
...
}
And here is what I am expecting to be in Mongo collection:
{
"_id": ObjectId("5fba805dfdaaa760974d45de"),
"id": "42"
}
By default, spring-mongodb maps id property to _id field in Mongo. I know that the simplest way to avoid this is to put #org.springframework.data.mongodb.core.mapping.Field("id") annotation on id property in a Java class. But I prefer not to use this annotation since I would like to keep my model independent of Mongo, or Spring, or whatever. Which options are possible here?
Here is what I have tried or checked:
Registering custom AbstractMongoEventListener in order to modify Mongo documents just before they are written to Mongo, or just after they have been read from Mongo. It does not work for me since custom listener is called only during get and insert operations, but not during the update or upsert operations (see discussion here for details).
Providing custom FieldNamingStrategy — it does not work since in the spring-data-mongodb code they use strategy only if field name is not id or _id.
Providing custom converter for each of my classes. I believe it should work. But this approach seems to be too complicated since I have many classes with many properties in each of them and I'm not sure I would like to have many converters with boilerplate code inside.
Any help would be appreciated.
Don't you think that id with _id is already confusing?
If you have a strong valid reason to have an id (different than DB one); give it a name .e.g: version_id, old_id, other_services_id ... some meaningful name.
And yes using #Field in your case is the simplest way

Spring-Data-Cassandra SchemaAction not working

I'm trying to configure Cassandra in my application by extending CassandraAutoConfiguration
I'm using spring CassandraRepository for DB access and classes with o.s.d.cassandra.core.mapping.Table annotation for defining my tables.
I've also configured following property, along with other required properties for cluster
spring:
data:
cassandra:
schema-action: CREATE_IF_NOT_EXISTS
But No table get created in Cassandra upon application startup.
schemaAction in CassandraProperties is not working.
If I programmatically create tables upon startup in my ApplicationRunner by using cassandraTemplate.getCqlOperations().execute(...) then everything works fine.
In this case I am able to use my repository. find() and save() methods.
Just to prove that my #table classes are correctly written
Here is the behaviour I noticed. This is not only true for this particular key in application.yaml
When you don't create any bean extending AbstractCassandraConfiguration spring-data will read every key matching spring.data.* in application.yaml including the schema-action you provided. (by CONVENTION). I don't seen any issue with the file you provided, as a matter of fact I have a working sample here
When you create a bean extending AbstractCassandraConfiguration, now this is your job to implements explicitly the values you want as such please add in your class. Also you will need ro provide explicitly annotation #EnableCassandraRepositories
#Value("${spring.data.cassandra.schema-action}")
private String schemaAction;
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.valueOf(schemaAction);
}
On top of this I would like to advise NOT USING IT AT ALL. Spring Data works like a charm but here are my concerns:
Creating a table is not only a matter of matching the data model. Indeed what about Compaction Strategy based on your use case or TTL or any metadata.
We assume you know how to build a primary key properly with Partitions key and Clustering column but what if you need to store the exact same object in 2 tables because you have 2 different queries on it. (remember: I you need ALLOW FILTERING anywhere in your application=> your data model is probably wrong.

Mongo Spring #Document annotation

I'm trying to work with MongoTemplate and Spring and while looking on some other peoples code I was that everyone uses #Document annotation.
I did not used it at all up until not and everything worked fine for me.
I'm afraid I'm missing something and could not find any specific detailed information about the benefits of #Document annotation.
#Document is an annotation provided by Spring data project.
It is used to identify a domain object, which is persisted to MongoDB.
So you can use it to map a Java class into a collection inside MongoDB.
If you don't use Spring Data, you don't need this annotation.
I wrote a German blog post about how to use Spring Data for MongoDB - #Document is used there also:
https://agile-coding.blogspot.com/2020/10/keine-ahnung-von-mongodb-dann-nimm.html
This annotation serves only for specifying collection properties.
You can basically create simple class without any annotation needed.
Document annotation serves you for example when you are not happy with autogenerated collection name. If you do not specify #Document annotation, you still have the same result, for example
[
{
"_id": {"$oid": "62b43525de57ec7dec41a286"},
"_class": "com.example.demo.Person",
"name": "Adam"
}
]
This annotation may however be important for some kind of annotation based process to target which document are in use (maybe for indexing and so on)
Same goes for Id annotation:
if you do not provide any id specification, Id field is automatically added by mongo
if you have field named as "id" of type BigInteger, ObjectId or String, then this field is automatically populated after insert
From the docs https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mongo-template.id-handling
11.5.1. How the _id Field is Handled in the Mapping Layer
MongoDB requires that you have an _id field for all documents. If you
do not provide one, the driver assigns an ObjectId with a generated
value. When you use the MappingMongoConverter, certain rules govern
how properties from the Java class are mapped to this _id field:
A property or field annotated with #Id (org.springframework.data.annotation.Id) maps to the _id field.
A property or field without an annotation but named id maps to the _id field.
The following outlines what type conversion, if any, is done on the
property mapped to the _id document field when using the
MappingMongoConverter (the default for MongoTemplate).
If possible, an id property or field declared as a String in the Java class is converted to and stored as an ObjectId by using a Spring
Converter<String, ObjectId>. Valid conversion rules are delegated to
the MongoDB Java driver. If it cannot be converted to an ObjectId,
then the value is stored as a string in the database.
An id property or field declared as BigInteger in the Java class is converted to and stored as an ObjectId by using a Spring
Converter<BigInteger, ObjectId>.
If no field or property specified in the previous sets of rules is
present in the Java class, an implicit _id file is generated by the
driver but not mapped to a property or field of the Java class.
When querying and updating, MongoTemplate uses the converter that
corresponds to the preceding rules for saving documents so that field
names and types used in your queries can match what is in your domain
classes.
Some environments require a customized approach to map Id values such
as data stored in MongoDB that did not run through the Spring Data
mapping layer. Documents can contain _id values that can be
represented either as ObjectId or as String. Reading documents from
the store back to the domain type works just fine. Querying for
documents via their id can be cumbersome due to the implicit ObjectId
conversion. Therefore documents cannot be retrieved that way. For
those cases #MongoId provides more control over the actual id mapping
attempts. Example 62. #MongoId mapping
public class PlainStringId { #MongoId String id; }
public class PlainObjectId { #MongoId ObjectId id; }
public class StringToObjectId { #MongoId(FieldType.OBJECT_ID) String id; }
The id is treated as String without further conversion. The id is
treated as ObjectId. The id is treated as ObjectId if the given
String is a valid ObjectId hex, otherwise as String. Corresponds to
#Id usage.

Expose custom query in Spring Boot Rest API with multiple joins

I have an Spring REST Api and a MySQL Database, now I would like to expose the result of an custom query with multiple joins.
I have tried multiple suggestions that I found online but none of them were working for me so far.
What I want to do is something like a read only DTO that has all the fields of my custom query so that in the end I have one api page exposing the DTO data as JSON so my client (Angular) can read the data from there.
I already tried to:
create an #RestController with an injected EntityManager that executes a NativeQuery and then populates the DTO with the returned data but since my DTO is no Entity I get an Hibernate Mapping Exception
create a custom Repository and its Impl but with a similar outcome
place the Query inside an existing #Entity that is part of the Query statement
What am I missing here? Do I have to annotate my DTO maybe? Cuttently it's just a POJO, I think the #Entity annotation is not the right thing here since I don't want a Table created from my DTO.
Fixed it by letting the Query return an Array of type Object and afterwards mapping it to the DTO Constructor.

How do I execute named queries from a JPA EntityListener?

I have a requirement to set a date_updated value in my database for each row when that row is updated. Let's call the entity that I'm working with Order, which has a corresponding orders table in the database.
I've added the date_updated column to the orders table. So far, so good.
The #Entity Order object that I'm working with is provided by a third party. I do not have the ability to modify the source code to add a field called dateUpdated. I have no requirement to map this value to the object anyway - the value is going to be used for business intelligence purposes only and does not need to be represented in the Java entity object.
My problem is this: I want to update the date_updated column in the database to the current time each time an Order object (and its corresponding database table row) is modified.
Constraints:
We are using Oracle, Spring, JPA and Hibernate
I cannot use Oracle triggers to update the value. We are using a database replication technology that prevents us from using triggers.
My approach thus far has been to use a JPA EntityListener, defined in xml, similar to this:
<entity-mappings xmlns="....">
<entity class="com.theirs.OrderImpl">
<entity-listeners>
<entity-listener class="com.mine.listener.OrderJPAListener" />
</entity-listeners>
</entity>
</entity-mappings>
My listener class looks like this:
public class OrderJPAListener {
#PostPersist
#PostUpdate
public void recordDateUpdated(Order order) {
// do the update here
}
}
The problem I'm having is injecting any sort of persistence support (or anything at all, really) into my listener. Because JPA loads the listener via its methods, I do not have access to any Spring beans in my listener class.
How do I go about injecting an EntityManager (or any Spring bean) into my listener class so that I can execute a named query to update the date_updated field?
How do I go about injecting an EntityManager (or any Spring bean) into
my listener class so that I can execute a named query to update the
date_updated field?
As noted above JPA 2.1 supports injecting managed beans to an Entity Listener via CDI. Whether or not Spring supports this I am not sure. The folloiwng post proposes a Spring specific solution.
https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/
A possible alternative approach would be however to override the SQL generated by Hibernate on an update which is possible as detailed below.
https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/querysql.html#querysql-cud
This would be straightforward if you had the source as you would just need to add the #SQLUpdate annotation and tag on the additional date_update column. As you don't however you would need to look at redefining the metadata for that Entity via an xml configuration file and defining the sql-update statement as outlined above:
https://docs.jboss.org/hibernate/stable/annotations/reference/en/html/xml-overriding.html#xml-overriding-principles-entity
Since JPA 2.1 Entity Listeners are CDI managed. Have you tried using #PersistenceUnit annotation? Are you using JTA transaction type?
Otherwise you could use Persistence.createEntityManagerFactory within the Listener class to retrieve the Persistence Context.

Resources