Spring Couchbase CrudRepository type safety? - java

I'm evaluating Spring Data's support for Couchbase, and have have run across the following issue. Consider the following pseudo-code example where I've two POJO classes, and a repository defined and instantiated for each:
public class Foo
{
#Id
private String _id;
#Version
private Long _rev;
// .. Other data and members.
}
public class Bar
{
#Id
private String _id;
#Version
private Long _rev;
// .. Other data and members.
}
//
// Repositories
//
#Repository
public interface FooRepository extends CrudRepository<Foo, String> {
}
#Repository
public interface BarRepository extends CrudRepository<Bar, String> {
}
Both repositories are utilizing the same Couchbase bucket. Next:
// Create a new Foo object and save it.
Foo f = new Foo( "id_1" );
fooRepository.save( f );
// Now, try fetching a Bar object using the ID of a Foo object (?)
Bar b = barRepository.findOne( "id_1" );
This results in a Bar object being returned, but not properly initialized - no exceptions are raised. The question, is why isn't an error indicated in this scenario? It seems like not much of a stretch to raise an exception when the requested type doesn't match the persisted type. Am I missing something?
FWIW, When I look at the raw documents in Couchbase via the admin console, I observe that each contains a "_class" property with presumably could be used to identify classes used to represent the data, and detect such mis-matches.

The problem here is that a document (JSON serialized entity) is stored associated to the id (#Id field) on same bucket as other entities, this generates a kind of ambiguity, saving Entity1 with id 1, will overwrite Entity2 with id 1.
An application side solution would be to store entities with distinct keys, in the case of Entity1 with id 1 something like: 1-Entity1 as key.
Maybe you could solve the problem with an approach similar to this one:
public Entity1 {
private static final String ENTITY_NAME = "ENTITY1";
#Id
private String key;
public void setKey(String key) {
this.key = key;
}
public String getKey(String key) {
return this.key;
}
public void setId(String id) {
this.key = ENTITY_NAME + ":" + id;
}
public String getId() {
if (null == this.key)
return null;
return this.key.substring(ENTITY_NAME.length()+1);
}
}

Related

Discord JDA IdLong Arraylist<Arraylist<Long>

I'm trying to figure out how can i store Long info about a Discord user in an array.
Tommy on discord type: ;create
then
Mike on discord type : ;create
public class Create extends ListenerAdapter {
public void onMessageReceived(MessageReceivedEvent event) {
if (!event.isFromGuild()) return;
String[] messageSent = event.getMessage().getContentRaw().split(" "); //for further code
String name = event.getMember().getUser().getName();
long idLong = event.getMember().getUser().getIdLong();
String idString = event.getMember().getUser().getId(); //not for Stackoverflow question
long base = 1L; //everyone start with 1 (L is because we are using Long version of int value)
if (messageSent[0].equalsIgnoreCase(";Create")) {
ArrayList<Long> dcbase = new ArrayList<>(); //Long is used to store getIdLong value
dcbase.add(idLong); //
dcbase.add(base); // 1L is default value
event.getChannel().sendMessage("Here is the array "+ dcbase).queue();}}
Now the problem is if i want my ArrayList to be for many user I would need an ArrayList of ArrayList. Arraylist<ArrayList<Long>>
But to search through them I would like to do search using the IdLong value.
I tried to replace dcbase as idLong but its already defined.
Is there any way i can do that?
Because what i want to do next is have a method that goes to the idLong of Tommy and pull out the [1] of the Tommy ArrayList.
I plan to store the info to a file that way and will have longer Arrays:
177877878787 1 0 0 //Tommy IdLong, base Long, stuff i'll add, stuff i'll add
121244777778 1 //Mike IdLong, base Long
//New line for new member.
Since I don't know on which line the required IdLong will be stored in the file, i need a reference to search it.
I am self-thaught, I hope I am clear enough.
You want a Map. The key is the user id (as a long) and the value is your number:
private final Map<Long, Long> map = new HashMap<>();
#Override
public void onMessageReceived(MessageReceivedEvent event) {
...
map.put(id, base);
...
}
You can then simply access the value by using map.get(id). Instead of using lists, I would recommend using proper objects with defined fields for whatever data you want to store.
For the case of persistence, instead of just writing your data to some file, use a relational database like PostgreSQL or SQLite. This way you can easily update them at runtime without having to read/write the entire map every time you want to access it.
A very helpful thing to use for this is the Java Persistence API (JPA).
#Entity
#Table
public class UserInfo {
#Id
private long id;
private long base;
public UserInfo(long id, long base) {
this.id = id;
this.base = base;
}
public void setId(long id) {
this.id = id;
}
public long getId() { return this.id; }
public void setBase(long base) {
this.base = base;
}
public long getBase() { return this.base; }
}

Hibernate Search/Lucene range query with enum field doesn't return any results

When using Hibernate Search with the following enum field:
public class UserSkill {
#Enumerated
#Field
private UserSkillLevel level; // <- this is the field we're using on the ranged query
}
public class User { // <- this is the root class
#IndexedEmbedded
private Set<UserSkill> skills = new HashSet<>();
}
This is the enum:
public enum UserSkillLevel {
JUNIOR,
CONFIRMED,
ADVANCED,
EXPERT
}
When attempting to do a ranged query:
var fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
var queryBuilder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(User.class).get();
var bq = new BooleanQuery.Builder();
var q = queryBuilder.range().onField("skills.level").above(UserSkillLevel.ADVANCED).createQuery();
bq.add(q, BooleanClause.Occur.MUST);
return fullTextEntityManager.createFullTextQuery(bq.build(), User.class).getResultList();
The problem is that no results are being returned, even if a user has a advanced or expert skill.
Enums are indexes as strings by default, so the range you asked for is actually "every skill level whose name is alphabetically after the word 'ADVANCED'".
Thus I'd expect this to match everything instead; maybe you forgot to reindex your data after you changed your mapping?
Regardless... If you want to index your enums as their ordinal, implement a custom bridge:
public class EnumAsIntegerBridge implements MetadataProvidingFieldBridge {
#Override
public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
builder.field( name, FieldType.INTEGER );
}
#Override
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
Enum<?> enum = (Enum<?>) value;
if ( enum != null ) {
luceneOptions.addNumericFieldToDocument( name, enum.ordinal(), document );
}
}
}
Then use it in your mapping:
public class UserSkill {
#Enumerated
#Field(bridge = #FieldBridge(impl = EnumAsIntegerBridge.class))
private UserSkillLevel level;
}
public class User {
#IndexedEmbedded
private Set<UserSkill> skills = new HashSet<>();
}
And pass the ordinal instead of the enum to the query:
var q = queryBuilder.range().onField("skills.level").above(UserSkillLevel.ADVANCED.ordinal()).createQuery();

How to make Morphia set the value of a field as the value returned by a function call?

I'm implementing the "auto-increment" id using strategy described here:
http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/
Basically the value of the seqId field is set by calling an utility function that updates the counter on an auxiliary collection and returns the incremented value. Sounds great.
My issue is in mapping this to be used with Morphia. The tutorial suggests performing the insert (such as in the shell) like so:
db.users.insert(
{
seqId: getNextSequence("userid"),
name: "Sarah C."
}
I'm basically looking to do something like setting the POJO seqId field to something that Morphia will translate into an insert like the one above when I invoke save().
My POJO looks like this:
#Entity
public class User {
#Id
private Long id;
// THIS IS THE FIELD I WANT TO AUTO-INCREMENT
private Long seqId;
private String name;
...
}
The question is: How to make Morphia set the value of a field as the value returned by a function call?
I looked into using the #PrePresist annotation to perform this function call and getting the value, then setting it in the +_id field. That has several drawbacks such as making multiple calls to MongoDB instead of just one, and also the fact that my model objects don't have a reference to the datastore and I'd rather not mix up the concerns.
Is this possible? Any suggestions?
I'm on MongoDB 2.6.6 using the latest Java drivers.
Thanks!
PS: I'm aware that auto-increment is not recommended in large environments. I need it anyways for this specific scenario.
I'll describe the solution that's working for us quite well. Note that this supports auto increments on the class level and a subset of it — so you can count users or admin-users (user with an admin enum or whatever).
This contains the current value for each auto increment field, it's basically a reference:
#Entity(noClassnameStored = true)
public class AutoIncrementEntity {
#Id
protected String key;
protected Long value = 1L;
protected AutoIncrementEntity() {
super();
}
/**
* Set the key name — class or class with some other attribute(s).
*/
public AutoIncrementEntity(final String key) {
this.key = key;
}
/**
* Set the key name and initialize the value so it won't start at 1.
*/
public AutoIncrementEntity(final String key, final Long startValue) {
this(key);
value = startValue;
}
public Long getValue() {
return value;
}
}
In your persistence service, you could use the following to set / create the auto increment automatically:
public <E extends BaseEntity> ObjectId persist(E entity) {
// If it's a user and doesn't yet have an ID, set one; start counting from 1000.
if ((entity instanceof UserEntity) && (((UserEntity) entity).getUserId() == null)) {
((UserEntity) entity).setUserId(
generateAutoIncrement(entity.getClass().getName(), 1000L));
}
// Additionally, set an ID within each user group; start counting from 1.
if ((entity instanceof UserEntity) && (((UserEntity) entity).getRoleId() == null)) {
((UserEntity) entity).setRoleId(
generateAutoIncrement(entity.getClass().getName() + "-" + entity.getRole(), 1L));
}
mongoDataStore.save(entity);
return entity.getId();
}
/**
* Return a unique numeric value for the given key.
* The minimum value, set to 1 if nothing specific is required.
*/
protected long generateAutoIncrement(final String key, final long minimumValue){
// Get the given key from the auto increment entity and try to increment it.
final Query<AutoIncrementEntity> query = mongoDataStore.find(
AutoIncrementEntity.class).field("_id").equal(key);
final UpdateOperations<AutoIncrementEntity> update = mongoDataStore
.createUpdateOperations(AutoIncrementEntity.class).inc("value");
AutoIncrementEntity autoIncrement = mongoDataStore.findAndModify(query, update);
// If none is found, we need to create one for the given key.
if (autoIncrement == null) {
autoIncrement = new AutoIncrementEntity(key, minimumValue);
mongoDataStore.save(autoIncrement);
}
return autoIncrement.getValue();
}
And finally your entity:
#Entity(value = "user", noClassnameStored = true)
public class UserEntity extends BaseEntity {
public static enum Role {
ADMIN, USER,
}
private Role role;
#Indexed(unique = true)
private Long userId;
private Long roleId;
// Role setter and getter
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
}
There's nothing specific going on in the entity. All the logic is handled by the persistence service. I'm not using the #PrePersist, because you'd then need to put the persistence service into the entity, which doesn't sound like a good idea.

How to represent a collection in a POJO to be rendered as a select in Grails view

I have a grails app using persistence annotated POJOs for domain model. Grails generates controllers and views from them as expected, but one class is a puzzle for me.
I need to represent a collection of strings ( at the moment an ArrayList of strings ) in that is grails-view 'friendly' and will render as a drop-down.
The data in ArrayList is 'fairly' constant so I thought enum could be used for it, but I'm just not sure.
The class in question:
/**
* available categories:
* Airplane
* Rotorcraft
* Glider
* Lighter than air
* Powered lift
* Powered parachute
* Weight-shift-control
*/
#Entity
public class AircraftCategory {
public AircraftCategory(){
this.aircraftCategories.add("Airplane");
this.aircraftCategories.add("Rotorcraft");
this.aircraftCategories.add("Glider");
this.aircraftCategories.add("Lighter Than Air");
this.aircraftCategories.add("Powered Lift");
this.aircraftCategories.add("Powered Parachute");
this.aircraftCategories.add("Weight Shift Control");
}
long id;
private long version;
private ArrayList <String> aircraftCategories = new ArrayList<String>();
public ArrayList <String> getAircraftCategories() {
return aircraftCategories;
}
public void setAircraftCategories(ArrayList <String> aircraftCategories) {
this.aircraftCategories = aircraftCategories;
}
#Id
#GeneratedValue
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
The easiest thing to do is to push this into the DB as a "proper" domain object. It's a bit silly to have an object that is essentially just a name field, but it will render as you want it to in the scaffolded views (assuming you have the association to your actual object).
The other advantage is that you have an easy extension point in your application later in case you need to add more data to AircraftCategory, like abbreviation for example.
For another possible solution, this question is very similar.

Hibernate getting Id from createCriteria() result

I have a method that takes a list of entities (Classes) and does some computation. To describe my needs here is the simplest outline of the method (pseudo-code):
public void do(List<Class<?> entities) {
for (Class<?> entity : entities) {
List<?> list = session.createCriteria(entity).list();
for (Object o : list) {
System.out.println(o.getClass().getSimpleName() + " " + o.getId());
}
}
}
Is there a way that I can access/get the Id of o?
session.getIdentifier(o)
Well, maybe it'll get many critics but all your entity classes may implement this interface:
public interface EntityWithId {
Integer getId();
void setId(Integer id);
}
If your id's are not integers may be the interface could be:
public interface EntityWithId<T> {
T getId();
setId(T id);
}
public class Entity implements EntityWithId<String> {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
.
.
.
}
Anyway, I don't see why you'd want to get the IDs of all your entities, it's a strange solution for a strange requirement, hehe.
Not unless you have the property named the same on all entities, then you could use reflection to call the getter for that property. If they are all named something different, then you would get a NoSuchMethodException

Categories

Resources