I'm just start to learning this ORM, so maybe I've done something wrong. In entity I write OneToOne relation but greendao doesn't generate it. If I'm writing in entity constructor arguments for foreign key it just ignores this and makes it like this. So there is no property and column in table. Thank you.
Public:
#Entity(active = true)
public class Public {
#Id(autoincrement = true)
Long id;
int publicId;
#ToOne(joinProperty = "id")
private Category category; ...
#Generated(hash = 12945501)
public Public(Long id, int publicId) {
this.id = id;
this.publicId = publicId;
}
PublicDao:
public class PublicDao extends AbstractDao<Public, Long> {
public static final String TABLENAME = "PUBLIC";
/**
* Properties of entity Public.<br/>
* Can be used for QueryBuilder and for referencing column names.
*/
public static class Properties {
public final static Property Id = new Property(0, Long.class, "id", true, "_id");
public final static Property PublicId = new Property(1, int.class, "publicId", false, "PUBLIC_ID");
} ...
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"PUBLIC\" (" + //
"\"_id\" INTEGER PRIMARY KEY AUTOINCREMENT ," + // 0: id
"\"PUBLIC_ID\" INTEGER NOT NULL );"); // 1: publicId
}
My mistake. I should to add another field for it, and to write it in joinProperty.
Related
I learned that in Spring Data JDBC I need to implement many to many relationships by having a reference to the ID of one entity in the other entity:
public class Student {
#Id
private Long studentId;
private String studentName;
#MappedCollection(idColumn = "student_id", keyColumn = "course_id")
private Set<CourseRef> courses;
}
public class Course {
#Id
private Long courseId;
private String courseName;
}
#Table("student_course")
public class CourseRef {
#Id
private Long studentCourseId;
private Long courseId;
#MappedCollection(idColumn = "student_course_id", keyColumn = "test_score_id")
private List<TestScore> testScores;
}
public class TestScore {
#Id
private Long testScoreId;
private Integer value;
}
public interface StudentRepository implements CrudRepository<Student, Long> {
}
public interface CourseRepository implements CrudRepository<Course, Long> {
}
public class StudentRepositoryTest {
#Autowired
StudentRepository repository;
#Test
void testAddTestScore() {
Student student = repository.findById(1L).get();
assertNotNull(student);
Set<CourseRef> courses = student.getCourses();
CourseRef course = courses.stream().filter(c -> c.getCourseId() == 2).findFirst().orElse(null);
assertNotNull(course);
courses.remove(course);
course.addTestScore(TestScore.create(90);
courses.add(course);
students.setCourses(courses);
repository.save(student);
}
}
With this setup I have a student table, course table, student_course table, and test_score table that has a foreign key to a student_course id. But I'm having trouble adding a new test score. The repository is unable to save the updated student due to a foreign key constraint failure with the student_course_id column. I was wondering, is it possible to add new test scores with this approach, and if so would I need to create a new repository?
You didn't post your schema so I don't know what exactly went wrong, but I started with your code and created a working example from it: https://github.com/schauder/stackoverflow/tree/main/jdbc/three-way-reference
I took the liberty to simplify your property/column names since repeating the entity/table didn't add value for me.
The Domain Model
I put some hints of DDD on it, but didn't go the full 9 yards.
But I wanted at least to give some examples on how the logic for adding a TestScore belongs into the aggregate and not out side in the test or service. But this is not relevant for the mapping.
The important changes I made are:
I dropped the ids of CourseRef and TestScore. Ids in general are not neccesary on inner entities (not aggregate roots).
I had to reintroduce it to CourseRef due to a bug I found in the progress.
I simplified the code for adding a TestScore removing and then adding the course again is not necessary.
public class Course {
#Id
Long id;
String name;
static Course create(String name) {
Course course = new Course();
course.name = name;
return course;
}
}
class Student {
#Id
final Long id;
String name;
#MappedCollection(idColumn = "STUDENT_ID", keyColumn = "COURSE_ID")
Set<CourseRef> courses = new HashSet<>();
Student(Long id) {
this.id = id;
}
/**
* quality of life method to create students by name.
*/
public static Student create(String name) {
final Student student = new Student(null);
student.name = name;
return student;
}
/**
* The aggregate root should take care of whatever logic is necessary to add a course.
*/
void addCourse(Course course) {
final CourseRef ref = new CourseRef();
ref.courseId = AggregateReference.to(course.id);
courses.add(ref);
}
/**
* The aggregate root should take care of whatever logic is necessary to add a testscore.
* #param course
* #param score
*/
public void addScore(Course course, int score) {
courses.stream()
.filter(c -> c.courseId.getId().equals(course.id))
.findFirst()
.orElseThrow()
.testScores.add(TestScore.create(90));
}
}
#Table("STUDENT_COURSE")
class CourseRef {
#Id // work around for issue https://github.com/spring-projects/spring-data-jdbc/issues/1139
Long Id;
AggregateReference<Course,Long> courseId;
#MappedCollection(idColumn = "STUDENT_COURSE_ID", keyColumn = "INDEX")
List<TestScore> testScores;
}
class TestScore {
private Integer value;
public static TestScore create(int value) {
final TestScore testScore = new TestScore();
testScore.value = value;
return testScore;
}
}
Database Schema
I added one:
CREATE TABLE COURSE
(
ID INTEGER IDENTITY PRIMARY KEY,
NAME VARCHAR(200) NOT NULL
);
CREATE TABLE STUDENT
(
ID INTEGER IDENTITY PRIMARY KEY,
NAME VARCHAR(200) NOT NULL
);
CREATE TABLE STUDENT_COURSE
(
ID INTEGER IDENTITY PRIMARY KEY,
STUDENT_ID INTEGER NOT NULL,
COURSE_ID INTEGER NOT NULL,
UNIQUE (STUDENT_ID, COURSE_ID),
FOREIGN KEY (STUDENT_ID) REFERENCES STUDENT(ID),
FOREIGN KEY (COURSE_ID) REFERENCES COURSE(ID)
);
CREATE TABLE TEST_SCORE
(
STUDENT_COURSE_ID INTEGER,
INDEX INTEGER,
VALUE INTEGER,
PRIMARY KEY (STUDENT_COURSE_ID, INDEX),
FOREIGN KEY (STUDENT_COURSE_ID) REFERENCES STUDENT_COURSE(ID)
);
The Test
#DataJdbcTest
class StudentRepositoryTest {
#Autowired
StudentRepository students;
#Autowired
CourseRepository courses;
Student jens = null;
#BeforeEach
void setup() {
Course physics = courses.save(Course.create("Physics"));
Course math = courses.save(Course.create("Math"));
Course informatics = courses.save(Course.create("Informatics"));
jens = Student.create("Jens");
jens.addCourse(physics);
jens.addCourse(math);
jens.addCourse(informatics);
jens = students.save(jens);
}
#Test
void testAddTestScore() {
Student student = students.findById(jens.id).orElseThrow();
assertNotNull(student);
Course math = courses.findByName("Math");
assertNotNull(math);
student.addScore(math, 90);
students.save(student);
}
}
I have table as below:
CREATE TABLE recipes
(
id INT AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
components JSON,
active BOOLEAN NULL DEFAULT TRUE,
PRIMARY KEY (id),
UNIQUE KEY (name)
)
CHARACTER SET "UTF8"
ENGINE = InnoDb;
I have created pojo class like below:
#JsonIgnoreProperties(ignoreUnknown = true)
public class CValueRecipeV2
{
#JsonProperty("components")
#JsonAlias("matcher.components")
#Column(name = "components")
#Valid
private List<CComponentV2> mComponents;
#JsonProperty("name")
#Column(name = "name")
private String name;
public List<CComponentV2> getComponents()
{
return mComponents;
}
public void setComponents(List<CComponentV2> mComponents)
{
this.mComponents = mComponents;
}
public String getName()
{
return mName;
}
public void setName(String mName)
{
this.mName = mName;
}
}
another class
#JsonIgnoreProperties(ignoreUnknown = true)
public class CComponentV2
{
#JsonProperty("shingle_size")
#JsonAlias("shingleSize")
#CShingleField
private Integer mShingleSize;
public Integer getmShingleSize()
{
return mShingleSize;
}
public void setmShingleSize(Integer mShingleSize)
{
this.mShingleSize = mShingleSize;
}
}
Now I am trying to fetch the record from the database using JOOQ.
But I am not able to convert json component string into component class.
I am reading the data from the table as mentioned below:
context.dsl().select(RECIPES.asterisk())
.from(RECIPES)
.where(RECIPES.NAME.eq(name))
.fetchInto(CValueRecipeV2.class);
In the database, I have the following record.
ID name components active
1 a [{"shingle_size=2"}] true
While fetching the data, I am receiving the following error
Caused by: org.jooq.exception.DataTypeException: Cannot convert from {shingle_size=2} (class java.util.HashMap) to class com.ac.config_objects.CComponentV2
I am new to JOOQ. Please let me know if I missing anything.
Thanks in advance.
I have solved my problem using the jooq converter.
var record = context.dsl().select(RECIPES.asterisk())
.from(RECIPES)
.where(RECIPES.NAME.eq(name))
.fetchOne();
record.setValue(RECIPES.COMPONENTS, record.get(RECIPES.COMPONENTS, new CComponentV2Converter()));
var recipe = record.into(CValueRecipeV2.class);
and my converter lools like below:
public class CComponentV2Converter implements Converter<Object, List<CComponentV2>>
{
static final long serialVersionUID = 0;
#Override
public List<CComponentV2> from(Object databaseObject)
{
var componentList = CObjectCaster.toMapList(databaseObject);
List<CComponentV2> cComponentV2s = new ArrayList<>();
componentList.forEach(e -> {
CComponentV2 cComponentV2 = new CComponentV2();
cComponentV2.setmShingleSize(CObjectCaster.toInteger(e.get("shingle_size")));
cComponentV2s.add(cComponentV2);
});
return cComponentV2s;
}
}
jOOQ doesn't understand your #JsonProperty and other annotations out of the box. You will have to implement your own record mapper to support them:
https://www.jooq.org/doc/latest/manual/sql-execution/fetching/pojos-with-recordmapper-provider/
I want to create a sequence with prefix "CID_00001" (example):
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private String id;
Is there any way to do this ?
You can do that using a custom id generator.
The easiest way is to extend Hibernate's SequenceStyleGenerator, which implements the access to the database sequence (incl. a few interesting optimizations)
public class StringPrefixedSequenceIdGenerator extends SequenceStyleGenerator {
public static final String VALUE_PREFIX_PARAMETER = "valuePrefix";
public static final String VALUE_PREFIX_DEFAULT = "";
private String valuePrefix;
public static final String NUMBER_FORMAT_PARAMETER = "numberFormat";
public static final String NUMBER_FORMAT_DEFAULT = "%d";
private String numberFormat;
#Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
return valuePrefix + String.format(numberFormat, super.generate(session, object));
}
#Override
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
super.configure(LongType.INSTANCE, params, serviceRegistry);
valuePrefix = ConfigurationHelper.getString(VALUE_PREFIX_PARAMETER, params, VALUE_PREFIX_DEFAULT);
numberFormat = ConfigurationHelper.getString(NUMBER_FORMAT_PARAMETER, params, NUMBER_FORMAT_DEFAULT);
}
}
After you've implemented your own id generator, you can reference it in a #GenericGenerator annotation.
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "book_seq")
#GenericGenerator(
name = "book_seq",
strategy = "org.thoughts.on.java.generators.StringPrefixedSequenceIdGenerator",
parameters = {
#Parameter(name = StringPrefixedSequenceIdGenerator.INCREMENT_PARAM, value = "50"),
#Parameter(name = StringPrefixedSequenceIdGenerator.VALUE_PREFIX_PARAMETER, value = "CID_"),
#Parameter(name = StringPrefixedSequenceIdGenerator.NUMBER_FORMAT_PARAMETER, value = "%05d") })
private String id;
...
}
I think you are using the database sequence for generating the PK. So add a trigger before inserting into the table. e.g., (in oracle )
CREATE OR REPLACE TRIGGER sometable_trigger
BEFORE INSERT ON SomeTable
FOR EACH ROW
BEGIN
SELECT 'CID_' || to_char(sometable_seq.NEXTVAL, "00009")
INTO :new.id
FROM dual;
END;
/
assumption: sequence name as "sometable_seq" and id column name id
I have 2 tables parent and child, parent as 1 Primary Key and a composite unique key(Combination of 2 columns[UN_KEY1, UN_KEY2]) . Now in the child table i refer these 2 columns as foreign keys. When i try to generate the entity in eclipse it show Many to one relationship and displays the parent columns. But when i generate these two columns are not generated in the Parent Entity. How to do transaction like add, update, delete in these entity without those columns
Table
--drop table "TBL_PARENT";
CREATE TABLE "TBL_PARENT"(
"S_ID" INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1, INCREMENT BY 1, NO MINVALUE, NO MAXVALUE, CACHE 20),
"UN_KEY1" Integer NOT NULL,
"UN_KEY2" Smallint NOT NULL,
"SOME_COL1" Integer
);
-- Add keys for table TBL_PARENT
ALTER TABLE "TBL_PARENT" ADD CONSTRAINT "TBL_PARENTKEY3" PRIMARY KEY ("S_ID");
ALTER TABLE "TBL_PARENT" ADD CONSTRAINT "TBL_PARENTKey4" UNIQUE ("UN_KEY1","UN_KEY2");
--drop table "TBL_PARENT";
CREATE TABLE "TBL_CHILD"(
"S_ID" Integer NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1, INCREMENT BY 1, NO MINVALUE, NO MAXVALUE, CACHE 20),
"UN_KEY1" Integer,
"UN_KEY2" Integer,
"SOME_COL2" Integer
);
ALTER TABLE "TBL_CHILD" ADD CONSTRAINT "TBL_CHILD_KEY3" PRIMARY KEY ("S_ID");
ALTER TABLE "TBL_CHILD" ADD CONSTRAINT "TBL_CHILD_FK" FOREIGN KEY ("UN_KEY1", "UN_KEY2") REFERENCES "TBL_PARENT" ("UN_KEY1", "UN_KEY2");
Generated Code:
#Entity
#Table(name="TBL_PARENT")
#NamedQuery(name="TblParent.findAll", query="SELECT t FROM TblParent t")
public class TblParent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="S_ID")
private int sId;
#Column(name="SOME_COL1")
private int someCol1;
//bi-directional many-to-one association to TblChild
#OneToMany(mappedBy="tblParent")
private Set<TblChild> tblChilds;
public TblParent() {
}
public int getSId() {
return this.sId;
}
public void setSId(int sId) {
this.sId = sId;
}
public int getSomeCol1() {
return this.someCol1;
}
public void setSomeCol1(int someCol1) {
this.someCol1 = someCol1;
}
public Set<TblChild> getTblChilds() {
return this.tblChilds;
}
public void setTblChilds(Set<TblChild> tblChilds) {
this.tblChilds = tblChilds;
}
public TblChild addTblChild(TblChild tblChild) {
getTblChilds().add(tblChild);
tblChild.setTblParent(this);
return tblChild;
}
public TblChild removeTblChild(TblChild tblChild) {
getTblChilds().remove(tblChild);
tblChild.setTblParent(null);
return tblChild;
}
}
#Entity
#Table(name="TBL_CHILD")
#NamedQuery(name="TblChild.findAll", query="SELECT t FROM TblChild t")
public class TblChild implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="S_ID")
private int sId;
#Column(name="SOME_COL2")
private int someCol2;
//bi-directional many-to-one association to TblParent
#ManyToOne
#JoinColumns({
#JoinColumn(name="UN_KEY1", referencedColumnName="UN_KEY1"),
#JoinColumn(name="UN_KEY2", referencedColumnName="UN_KEY2")
})
private TblParent tblParent;
public TblChild() {
}
public int getSId() {
return this.sId;
}
public void setSId(int sId) {
this.sId = sId;
}
public int getSomeCol2() {
return this.someCol2;
}
public void setSomeCol2(int someCol2) {
this.someCol2 = someCol2;
}
public TblParent getTblParent() {
return this.tblParent;
}
public void setTblParent(TblParent tblParent) {
this.tblParent = tblParent;
}
}
Add the following to TblParent object
#Column(name = "UN_KEY1")
private int uniqueKey1;
#Column(name = "UN_KEY2")
private int uniqueKey2;
When creating a new object you will be doing the following
TblParent p = new TblParent();
p.setSId(1);
p.setSomeCol1(12);
p.setUniqueKey1(12);
p.setUniqueKey2(14);
p.setTblChilds(new HashSet<TblChild>());
TblChild c = new TblChild();
c.setSId(1);
c.setSomeCol2(14);
c.setTblParent(p);
p.getTblChilds.add(c);
When you create this parent object the framework will automatically add the foreign key values to TblChild table.
I'm currently working on a project where I'm trying to get a list of enities from table which does not have a primary key (dk_systemtherapie_merkmale). This table is 1:n related to another table (dk_systemtherapie). See the screenshot for the table structure.
When getting an entry for dk_systemtherapie, the program fetches the Collection "dkSystemtherapieMerkmalesById". However, the first table entry is fetched as often as the number of actual entries in the table is. It never fetches the other entries from dk_systemtherapie_merkmale. I assume it has something to do with the fact that hibernate can't differ between the entries, but I don't know how to fix it.
Table schema
I've created two corresponding entity classes, dk_systemtherapie:
#Entity
#Table(name = "dk_systemtherapie", schema = "***", catalog = "")
public class DkSystemtherapieEntity {
private int id;
private Collection<DkSystemtherapieMerkmaleEntity> dkSystemtherapieMerkmalesById;
#Id
#Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#OneToMany(mappedBy = "dkSystemtherapieByEintragId")
public Collection<DkSystemtherapieMerkmaleEntity> getDkSystemtherapieMerkmalesById() {
return dkSystemtherapieMerkmalesById;
}
public void setDkSystemtherapieMerkmalesById(Collection<DkSystemtherapieMerkmaleEntity> dkSystemtherapieMerkmalesById) {
this.dkSystemtherapieMerkmalesById = dkSystemtherapieMerkmalesById;
}
}
Here the second one, which is accessing the table without a primary key, dk_systhemtherapie_merkmale:
#Entity #IdClass(DkSystemtherapieMerkmaleEntity.class)
#Table(name = "dk_systemtherapie_merkmale", schema = "***", catalog = "")
public class DkSystemtherapieMerkmaleEntity implements Serializable {
#Id private Integer eintragId;
#Id private String feldname;
#Id private String feldwert;
private DkSystemtherapieEntity dkSystemtherapieByEintragId;
#Basic
#Column(name = "eintrag_id")
public Integer getEintragId() {
return eintragId;
}
public void setEintragId(Integer eintragId) {
this.eintragId = eintragId;
}
#Basic
#Column(name = "feldname")
public String getFeldname() {
return feldname;
}
public void setFeldname(String feldname) {
this.feldname = feldname;
}
#Basic
#Column(name = "feldwert")
public String getFeldwert() {
return feldwert;
}
public void setFeldwert(String feldwert) {
this.feldwert = feldwert;
}
#Id
#ManyToOne
#JoinColumn(name = "eintrag_id", referencedColumnName = "id")
public DkSystemtherapieEntity getDkSystemtherapieByEintragId() {
return dkSystemtherapieByEintragId;
}
public void setDkSystemtherapieByEintragId(DkSystemtherapieEntity dkSystemtherapieByEintragId) {
this.dkSystemtherapieByEintragId = dkSystemtherapieByEintragId;
}
}
I assume the problem is releated to the fact that Hibernate is using the following annotation as the one and only id for fetching data from database.
#Id
#ManyToOne
#JoinColumn(name = "eintrag_id", referencedColumnName = "id")
public DkSystemtherapieEntity getDkSystemtherapieByEintragId() {
return dkSystemtherapieByEintragId;
}
This leads to the problem that when getting more than one entry with the same id (as the id is not unique), you will get the number of entries you would like to but hibernate is always fetching the first entry for this id. So in fact you are getting dublicate entries.
So how to fix this?
According to this question: Hibernate and no PK, there are two workarounds which are actually only working when you don't have NULL entries in your table (otherwise the returning object will be NULL as well) and no 1:n relationship. For my understanding, hibernate is not supporting entities on tables without primary key (documentation). To make sure getting the correct results, I would suggest using NativeQuery.
Remove the Annotations and private DkSystemtherapieEntity dkSystemtherapieByEintragId; (incl. beans) from DkSystemtherapieMerkmaleEntity.java und add a constructor.
public class DkSystemtherapieMerkmaleEntity {
private Integer eintragId;
private String feldname;
private String feldwert;
public DkSystemtherapieMerkmaleEntity(Integer eintragId, String feldname, String feldwert) {
this.eintragId = eintragId;
this.feldname = feldname;
this.feldwert = feldwert;
}
public Integer getEintragId() {
return eintragId;
}
public void setEintragId(Integer eintragId) {
this.eintragId = eintragId;
}
public String getFeldname() {
return feldname;
}
public void setFeldname(String feldname) {
this.feldname = feldname;
}
public String getFeldwert() {
return feldwert;
}
public void setFeldwert(String feldwert) {
this.feldwert = feldwert;
}
}
Remove private Collection<DkSystemtherapieMerkmaleEntity> dkSystemtherapieMerkmalesById; (incl. beans) from DkSystemtherapieEntity.java.
Always when you need to get entries for a particular eintrag_id, use the following method instead of the Collection in DkSystemtherapieEntity.java.
public List<DkSystemtherapieMerkmaleEntity> getDkSystemtherapieMerkmaleEntities(int id) {
Transaction tx = session.beginTransaction();
String sql = "SELECT * FROM dk_systemtherapie_merkmale WHERE eintrag_id =:id";
List<Object[]> resultList;
resultList = session.createNativeQuery(sql)
.addScalar("eintrag_id", IntegerType.INSTANCE)
.addScalar("feldname", StringType.INSTANCE)
.addScalar("feldwert", StringType.INSTANCE)
.setParameter("id", id).getResultList();
tx.commit();
List<DkSystemtherapieMerkmaleEntity> merkmale = new ArrayList<>();
for (Object[] o : resultList) {
merkmale.add(new DkSystemtherapieMerkmaleEntity((Integer) o[0], (String) o[1], (String) o[2]));
}
return merkmale;
}
Call getDkSystemtherapieMerkmaleEntities(dkSystemtherapieEntityObject.getid()) instead of getDkSystemtherapieMerkmalesById().