I have the model like following
#CompoundIndexes(value = {
#CompoundIndex(name = "catalog_idx", def = "{'code' : 1, 'brand' : 1}", unique = true) })
#Document(collection = Catalog.ENTITY)
public class Catalog extends AbstractModel<String> {
private static final long serialVersionUID = 1L;
public static final String ENTITY = "catalog";
#NotNull(message = "Code is required")
#Field("code")
private String code;
#NotNull(message = "Brand is required")
#DBRef(lazy = true)
#Field("brand")
private Brand brand;
}
When i do save with mongoTemplate.save(object); i see only 2 objects created in DB instead of 6. Just before save my debug lines for objects to be saved.
Catalog [code=StagedCatalog, brand=Brand [code=Brand_3]]
Catalog [code=StagedCatalog, brand=Brand [code=Brand_2]]
Catalog [code=StagedCatalog, brand=Brand [code=Brand_1]]
Catalog [code=OnlineCatalog, brand=Brand [code=Brand_2]]
Catalog [code=OnlineCatalog, brand=Brand [code=Brand_1]]
Catalog [code=OnlineCatalog, brand=Brand [code=Brand_3]]
Any ideas why ? I feel the Index unique thing is not working somehow. I want code and brand to be unique combination.
public abstract class AbstractModel<ID extends Serializable> implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private ID id;
}
You have set a unique index. It means that you will be unable to have 2 documents with the same code and brand.
Now you have set the ID column to ID object. The fact that you have 2 insert instead of 6 means that you use the same ID for 3 insert, something like :
for (code: {"StagedCatalog","OnlineCatalog"} ) {
ID id=new ID(...);
for (brand: {1, 2, 3}){
Catalog cat=new Catalog();
cat.setId(id); // <<== this is wrong, you reuse the same id, you will insert first brand, then update to brand2 and brand3.
cat.setCode(code);
cat.setBrand(brand);
mongoTemplate.persist(cat);
}
}
To prevent that, you need to:
Catalog cat=new Catalog();
ID id=new ID(realUniqueId); // RealuniqueId can be code+brand for instance
cat.setId(id);
...
db.collection.save()
Updates an existing document or inserts a new document, depending on its document parameter.
Document Structure
Related
I'm working with a third party database that is read-only. I have the following Spring Data repository:
public interface FolderRepository extends Repository<Folder, Integer> {
#Query(value = "SELECT f.folderId, a.fileId, a.fileName, " //selecting other columns in my app
+ "FROM User.functionSys(:binData) f "
+ "LEFT JOIN User.vFile a "
+ "ON f.fileId = a.fileId "
+ "group by f.folderId, a.fileId, a.fileName",
nativeQuery = true)
List<Folder> getFolders(#Param("binData") byte[] binData);
}
Folder id and file id form an unique key. So, my folder entity looks like this:
#Entity
#Data
public class Folder implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private FolderFileKey id;
private String fileName;
// several other fields in my app
}
#Embeddable
#Data
class FolderFileKey implements Serializable {
private static final long serialVersionUID = 2L;
private Integer folderId;
private Integer fileId;
}
The problem is that I really want a list of File objects where each File object contains a list of Folders (the same file id may be in several different folders):
#Data
public class FileDto {
private Integer fileId;
private String fileName;
private List<Folder> folders;
}
#Data
public class FolderDto {
private Integer folderId;
private String folderName;
}
I know that I could write a service to transform a Folder entity into the FileDto and FolderDto but is there a way to use Spring Data projections or rewrite the entities to achieve the structure wanted in the Dtos?
Update
User.functionSys(:binData) is a table-valued function (so it returns a table).
I have the following Spring Data MongoDB document:
#Document(collection = "messages")
#CompoundIndexes({ #CompoundIndex(name = "chatId_messageId", def = "{'chatId': 1, 'messageId': 1}") })
public class Message implements Serializable {
private static final long serialVersionUID = -1590112217771926691L;
#Id
private String id;
private Long chatId;
private Integer messageId;
private Post post;
}
the Post model(which is not Spring Data MongoDB document) looks like:
public class Post implements Serializable {
private static final long serialVersionUID = -886664249197573502L;
private String id;
}
I'd like to add index to the Message.post.id field.
How it can be done with Spring Data MongoDB and Message.post field declaration in Message document ?
If you want to add Message.post.id to an already compound index, then do it like
#Document(collection = "messages")
#CompoundIndexes({ #CompoundIndex(name = "chatId_messageId", def = "{'chatId': 1, 'messageId': 1, 'Message.post.id' : 1}") })
public class Message implements Serializable {
private static final long serialVersionUID = -1590112217771926691L;
#Id
private String id;
private Long chatId;
private Integer messageId;
private Post post;
}
Compound indexes are indexes that have more than one indexed field, so ideally, the most restrictive field should be to the left of the B-tree. If you want to index by sex and birth, for instance, the index should begin by birth, as it is much more restrictive than sex.
Or if you want to treat it as a saperate index, then create index using #Indexed like
public class Post implements Serializable {
private static final long serialVersionUID = -886664249197573502L;
#Indexed
private String id;
}
Updated
For more info regarding how queries with sub fields of compound index works, check Documentation
I have the above domain structure where I have list of Companies in the product and the aim is not make entry in mongoDB when I have exact match for companies & productId already present in the DB.
#Entity
public class Mobile {
#Id
private Integer id;
private String imei;
private Product productInfo;
// ...
}
#Entity
public class Product {
#Id
private Integer id;
private String productId;
private List<Company<?>> companies;
// ...
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(name= "samsung", value = Samsung.class),
#JsonSubTypes.Type(name= "htc",value = Htc.class)})
public class Company<T> implements Serializable {
private static final long serialVersionUID = -8869676577723436716L;
private T companyInfo;
private String type;
// ...
}
I am using mongo template and I have tried to use find as shown below but id didn't work
template.find(Query.query(Criteria.where("product.companies").is(companList),Mobile.class);
My application uses Hibernate 5.02 and Wildfly 10 with a PostgreSQL 9.5 database. I'm trying to enable a filter on a #OneToMany collection held within an entity that is constructed via a NamedQuery. Unfortunately, it seems as if the filter is just ignored. Here are the different components, redacted for ease of reading.
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "analystprocess_id IN (:processIds) AND id NOT IN (SELECT msg_id FROM analysis.analyzedmsg WHERE analyst_id IN (:analystIds) AND analystprocess_id IN (:processIds)) ORDER BY process_msg_id")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
private void buildAnalystProcess() {
LOG.info("Building AnalystProcessEntity");
analystUser.getJdbcSession().enableFilter(AnalystProcessEntity.MSG_FILTER)
.setParameter(AnalystProcessEntity.MSG_FILTER_PROC_ID_PARAM, analystProcessId)
.setParameter(AnalystProcessEntity.MSG_FILTER_ANALYST_ID_PARAM, analystUser.getId());
Query query = analystUser.getJdbcSession().getNamedQuery(AnalystProcessEntity.GET_PROCESS)
.setParameter("processId", analystProcessId);
// Query query = analystUser.getJdbcSession().createNativeQuery("SELECT * FROM analysis.analystprocess WHERE id = :processId")
// .setParameter("processId", analystProcessId)
// .addEntity(AnalystProcessEntity.class);
analystProcess = (AnalystProcessEntity) query.getSingleResult();
CREATE TABLE analysis.analystprocess (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_date TIMESTAMP NOT NULL DEFAULT now(),
...
);
CREATE TABLE analysis.msg (
id SERIAL PRIMARY KEY,
analystprocess_id INTEGER NOT NULL REFERENCES analysis.analystprocess(id) ON DELETE CASCADE ON UPDATE CASCADE,
process_msg_id INTEGER NOT NULL,
constraint tbl_statusid_analystprocessid unique(status_id, analystprocess_id)
);
As seen above, I have also tried the filter on constructing the AnalystProcessEntity class via createNativeQuery instead of getNamedQuery and no luck.
I also added a defaultCondition with hardcoded values into the #FilterDef just to see if it would execute the default condition and it still didn't.
I've tried the #Filter above the entity definition as well as above the class definition. I even came across a blog post which made it sound like the condition references entity fields (variable names) and not table fields (column names). Trying to stick to Java naming conventions in the Entity and Postgres naming conventions in the table, so I tried switching the references in the condition and to no avail.
I have sql logging turned on in Hibernate and the condition doesn't show up anywhere, as if it's just simply being ignored.
Any help would be greatly appreciated!
So, the problem was that I had the #FilterDef applied to the wrong class. It was my presumption that because I was constructing the AnalystProcessEntity which holds the MsgEntity collection (which I am trying to filter), that the #FilterDef would be applied to the AnalystProcessEntity class. Instead, it needs to be applied to the entity that it's actually filtering (hindsight being 20/20, that's pretty obvious).
Also, the actual condition needed to be modified to use complete references within the sub-select query.
I hope this helps someone at some point...
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "id NOT IN (SELECT amsg.msg_id FROM analysis.analyzedmsg amsg WHERE amsg.analyst_id IN (:analystIds) AND amsg.analystprocess_id IN (:processIds))")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
Additionally, I ran into another problem with null's appearing in the collection, despite the fact that I am using an #OrderColumn, which I thought fixed that issue. It seems that with the use of the #Filter, null's are inserted in place of what ended up being filtered OUT (excluded).
I have an #Entity class which holds an #ElementCollection:
#Entity
public class Skill extends JpaEntity {
#ElementCollection(targetClass = SkillName.class)
#CollectionTable(joinColumns = #JoinColumn(name = "SKILL_ID"))
private Set<SkillName> names = new HashSet<>();
...
Those elements are defined in a nested #Embeddable class without ID:
#Embeddable
#Immutable
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "NAME"))
public static class SkillName extends ValueObject {
private boolean selectable;
#Column(unique = true, nullable = false)
#Size(max = 64)
#NotEmpty
private String name;
...
I try to get some specific elements of that element-collection via Querydsl:
QSkill skill = QSkill.skill;
QSkill_SkillName skillName = QSkill_SkillName.skillName;
List<SkillName> foundSkillNames = from(skill)
.innerJoin(skill.names, skillName).where(...)
.list(skillName);
This gives me a MySQLSyntaxErrorException: Unknown column 'names1_.id' in 'field list' since the resulting query looks like:
select names1_.id as col_0_0_ from Skill skill0_ inner join Skill_names names1_ on ...
which is obviously wrong since SkillName has no id
If I replace .list(skillName) with .list(skillName.name) everything works fine, but I get a list of Strings instead of a list of SkillNames.
So the question is:
What can I do to get a list of #Embeddables of an #ElementCollection via Querydsl?
since you are looking for Embeddable objects inside an entity, you might navigate from the entity to the requested Embeddable (in your case "SkillName") - therefor your query should be changed to list(skill) - the entity:
List<Skill> list =
from(skill).innerJoin(skill.names, skillName).
where(skillName.name.like(str)).
list(skill);
for (Skill skill : list) {
// do something with
Set<SkillNames> skillNames = skill.getNames();
}
HTH
You cannot project Embeddable instances directly, but alternatively you can use
Projections.bean(SkillName.class, ...) to populate them or
Projections.tuple(...) to get the skillName properties as a Tuple instance