Assume the following (I'm using MySQL)
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class TclRequest2 {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private long id;
#Persistent(column = "userid")
#Column(jdbcType = "INTEGER", length = 11, allowsNull = "false", defaultValue = "1")
private Member member; // This object table is in another schema
// Getters and setters
}
The field member is persisted in another schema. I could solve this by specifying the "catalog" attribute in the Member class's #PersitentCapable annotation but that would kill the flexibility of specifying the schema name in the properties file I'm using since I'm configuring jdo in a properties file.
Thank you.
Related
Regarding to classes that can/may be used for entities in room whith Kotlin,
Is it mandatory to use data classes? Or could I use 'normal classes', i.e., the ones I use for the 'bussiness logic'
In case data classes are mandatory: can I add functionality to what they have by default? i.e, can I add functions (for whatever taks they may need) to them?
The documentation doesn't say anything about limiting entities to data classes (although each and every code snippet use a data class).
Thanks.
Is it mandatory to use data classes?
No, you can use either including a mix.
data classes are a convenience class
can I add functionality to what they have by default?
Yes.
Perhaps consider the following:-
#Entity
class Table1 {
#PrimaryKey
var id: Long? = null
var name: String = ""
#Ignore
var notAColumnInTheTable = false
constructor(){}
#Ignore
constructor(name: String) {
this.id = null
this.name = name
this.notAColumnInTheTable = true
}
fun getIdAndName(): String {
return id.toString() + ":" + name
}
}
and :-
#Entity
data class Table2(
#PrimaryKey
var id: Long? = null,
var name: String,
#Ignore
var notAColumnInTheTable: Boolean = false
) {
constructor(name: String) : this( id = null,name = name, notAColumnInTheTable = true)
fun getIdAndName(): String {
return id.toString() + ":" + name
}
}
basically they are the same.
Using :-
#Dao
abstract class Table1And2Dao {
#Insert
abstract fun insert(table1: Table1): Long
#Insert
abstract fun insert(table2: Table2): Long
#Query("SELECT * FROM table1")
abstract fun getAllFromTable1(): List<Table1>
#Query("SELECT * FROM table2")
abstract fun getAllFromTable2(): List<Table2>
}
note the use of an abstract class rather than the normally see interface
along with a suitable #Database annotated class, in this case one that has a function that returns an instance of the built database and for convenience/brevity allows running on the main thread.
Then using :-
var db = AppDatabase.getDatabase(this)
var dao = db.getTable1AndTable2Dao()
dao.insert(Table1("TABLE1_1"))
dao.insert(Table2("TABLE2_1"))
for(t1: Table1 in dao.getAllFromTable1()) {
Log.d("DBINFO","Name is ${t1.name} ID is ${t1.id} NotAColumnInTable is ${t1.notAColumnInTheTable} idandname = ${t1.getIdAndName()}")
}
for(t2: Table2 in dao.getAllFromTable2()) {
Log.d("DBINFO","Name is ${t2.name} ID is ${t2.id} NotAColumnInTable is ${t2.notAColumnInTheTable} idandname = ${t2.getIdAndName()}")
}
Results in the log including:-
D/DBINFO: Name is TABLE1_1 ID is 1 NotAColumnInTable is true idandname = 1:TABLE1_1
D/DBINFO: Name is TABLE2_1 ID is 1 NotAColumnInTable is true idandname = 1:TABLE2_1
Via App Inspection :-
and :-
I am facing issue while searching the json field. Any help would be appreciated.. here are the sample code snippet and error what I am facing
Entity class
#TypeDef(name = "cardjsonb", typeClass = JsonBinaryType.class)
public class TransactionEntity {
#Type(type = "cardjsonb")
#Column(name = "card_details", columnDefinition = "jsonb")
private TransactionCardDetails cardDetails;
}
public class TransactionCardDetails {
private Date expiryDate; //MM-YYYY
private String cardOnName;
private String cardNumber;
}
specification snippet
return (root, query, builder) -> builder.equal(
builder.function("json_extract_path_text", JsonBinaryType.class, root.get("cardDetails"), builder.literal("cardOnName")),
builder.literal("David")
);
Error details
ERROR 20628 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: function json_extract_path_text(jsonb, character varying) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Position: 1732
If you are using jsonb as input, you need to use the jsonb_extract_path_text function instead.
I am using Hibernate/JPA.
When i do an entity.save() or session.update(entity), hibernate generates a query like this :-
update TABLE1 set COL_1=? , COL_2=? , COL_3=? where COL_PK=?
Can I include an additional column in the WHERE clause by means of any annotation in the entity, so it can result in a query like :-
update TABLE1 set COL_1=? , COL_2=? , COL_3=? where COL_PK=? **AND COL_3=?**
This is because our DB is sharded based on COL_3 and this needs to be present in where clause
I want to be able to achieve this using the session.update(entity) or entity.save() only.
If I understand things correctly, essentially what you are describing is that you want hibernate to act like you have a composite primary key even though your database has a single-column primary key (where you also have a #Version column to perform optimistic locking).
Strictly speaking, there is no need for your hibernate model to match your db-schema exactly. You can define the entity to have a composite primary key, ensuring that all updates occur based on the combination of the two values. The drawback here is that your load operations are slightly more complicated.
Consider the following entity:
#Entity
#Table(name="test_entity", uniqueConstraints = { #UniqueConstraint(columnNames = {"id"}) })
public class TestEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false, unique = true)
private Long id;
#Id
#Column(name = "col_3", nullable = false)
private String col_3;
#Column(name = "value", nullable = true)
private String value;
#Version
#Column(nullable = false)
private Integer version;
... getters & setters
}
Then you can have the following method (in my case, I created a simple JUnit test)
#Test
public void test() {
TestEntity test = new TestEntity();
test.setCol_3("col_3_value");
test.setValue("first-value");
session.persist(test);
long id = test.getId();
session.flush();
session.clear();
TestEntity loadedTest = (TestEntity) session
.createCriteria(TestEntity.class)
.add(Restrictions.eq("id", id))
.uniqueResult();
loadedTest.setValue("new-value");
session.saveOrUpdate(loadedTest);
session.flush();
}
This generates the following SQL statements (enabled Hibernate logging)
Hibernate:
call next value for hibernate_sequence
Hibernate:
insert
into
test_entity
(value, version, id, col_3)
values
(?, ?, ?, ?)
Hibernate:
select
this_.id as id1_402_0_,
this_.col_3 as col_2_402_0_,
this_.value as value3_402_0_,
this_.version as version4_402_0_
from
test_entity this_
where
this_.id=?
Hibernate:
update
test_entity
set
value=?,
version=?
where
id=?
and col_3=?
and version=?
This makes loading slightly more complicated as you can see - I used a criteria here, but it satisfies your criteria, that your update statements always include the column col_3 in the 'where' clause.
The following solution works, however I recommend you to just wrap your saveOrUpdate method in a way that you ends up using a more natural approach. Mine is fine... but is a bit hacky.
Solution:
You can create your own annotation and inject your extra condition to hibernate save method using a hibernate interceptor. The steps are the following:
1. Create a class level annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface ForcedCondition {
String columnName() default "";
String attributeName() default ""; // <-- this one is just in case your DB column differs from your attribute's name
}
2. Annotate your entity specifying your column DB name and your entity attribute name
#ForcedCondition(columnName = "col_3", attributeName= "col_3")
#Entity
#Table(name="test_entity")
public class TestEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false, unique = true)
private Long id;
#Column(name = "col_3", nullable = false)
private String col_3;
public String getCol_3() {
return col_3;
}
... getters & setters
}
3. Add a Hibernate interceptor and inject the extra condition:
public class ForcedConditionInterceptor extends EmptyInterceptor {
private boolean forceCondition = false;
private String columnName;
private String attributeValue;
#Override
public boolean onSave(
Object entity,
Serializable id,
Object[] state,
String[] propertyNames,
Type[] types) {
// If your annotation is present, backup attribute name and value
if (entity.getClass().isAnnotationPresent(ForcedCondition.class)) {
// Turn on the flag, so later you'll inject the condition
forceCondition = true;
// Extract the values from the annotation
columnName = entity.getClass().getAnnotation(ForcedCondition.class)).columnName();
String attributeName = entity.getClass().getAnnotation(ForcedCondition.class)).attributeName();
// Use Reflection to get the value
// org.apache.commons.beanutils.PropertyUtils
attributeValue = PropertyUtils.getProperty(entity, attributeName);
}
return super.onSave(entity, id, state, propertyNames, types);
}
#Override
public String onPrepareStatement(String sql) {
if (forceCondition) {
// inject your extra condition, for better performance try java.util.regex.Pattern
sql = sql.replace(" where ", " where " + columnName + " = '" + attributeValue.replaceAll("'", "''") + "' AND ");
}
return super.onPrepareStatement(sql);
}
}
After all that everytime you call entity.save() or session.update(entity) over an entity annotated with #ForcedCondition, the SQL will be injected with the extra condition you want.
BTW: I didn't tested this code but it should get you along the way. If I did any mistake please tell me so I can correct.
I have a problem with column json when it's necessary to switching from PostgreSQL to MariaDB/MySql.
I use Spring Boot + JPA + Hibernate + hibernate-types-52.
The table i want to map is like this:
CREATE TABLE atable(
...
acolumn JSON,
...
);
Ok it works for PostgreSQL and MariaDB/MySql.
The problem is when i want to deploy an application that switch easly from one to another because the correct hibernate-types-52 implementation for PostgreSQL and MySQL/MariaDB are different
This works on MySQL/MariaDB
#Entity
#Table(name = "atable")
#TypeDef(name = "json", typeClass = JsonStringType.class)
public class Atable {
...
#Type(type = "json")
#Column(name = "acolumn", columnDefinition = "json")
private JsonNode acolumn;
...
}
This works on PosgreSQL
#Entity
#Table(name = "atable")
#TypeDef(name = "json", typeClass = JsonBinaryType.class)
public class Atable {
...
#Type(type = "json")
#Column(name = "acolumn", columnDefinition = "json")
private JsonNode acolumn;
...
}
Any kind of solutions to switch from JsonBinaryType to JsonStringType (or any other solution to solve this) is appreciated.
The Hypersistence Utils project, you can just use the JsonType, which works with PostgreSQL, MySQL, Oracle, SQL Server, or H2.
So, use JsonType instead of JsonBinaryType or JsonStringType
#Entity
#Table(name = "atable")
#TypeDef(name = "json", typeClass = JsonType.class)
public class Atable {
#Type(type = "json")
#Column(name = "acolumn", columnDefinition = "json")
private JsonNode acolumn;
}
That's it!
There are some crazy things you can do - with the limitation that this only works for specific types and columns:
First, to replace the static #TypeDef with a dynamic mapping:
You can use a HibernatePropertiesCustomizer to add a TypeContributorList:
#Configuration
public class HibernateConfig implements HibernatePropertiesCustomizer {
#Value("${spring.jpa.database-platform:}")
private Class<? extends Driver> driverClass;
#Override
public void customize(Map<String, Object> hibernateProperties) {
AbstractHibernateType<Object> jsonType;
if (driverClass != null && PostgreSQL92Dialect.class.isAssignableFrom(driverClass)) {
jsonType = new JsonBinaryType(Atable.class);
} else {
jsonType = new JsonStringType(Atable.class);
}
hibernateProperties.put(EntityManagerFactoryBuilderImpl.TYPE_CONTRIBUTORS,
(TypeContributorList) () -> List.of(
(TypeContributor) (TypeContributions typeContributions, ServiceRegistry serviceRegistry) ->
typeContributions.contributeType(jsonType, "myType")));
}
}
So this is limited to the Atable.class now and I have named this custom Json-Type 'myType'. I.e., you annotate your property with #Type(type = 'myType').
I'm using the configured Dialect here, but in my application I'm checking the active profiles for DB-specific profiles.
Also note that TypeContributions .contributeType(BasicType, String...) is deprecated since Hibernate 5.3. I haven't looked into the new mechanism yet.
So that covers the #Type part, but if you want to use Hibernate Schema generation, you'll still need the #Column(columnDefinition = "... bit, so Hibernate knows which column type to use.
This is where it start's feeling a bit yucky. We can register an Integrator to manipulate the Mapping Metadata:
hibernateProperties.put(EntityManagerFactoryBuilderImpl.INTEGRATOR_PROVIDER,
(IntegratorProvider) () -> Collections.singletonList(JsonColumnMappingIntegrator.INSTANCE));
As a demo I'm only checking for PostgreSQL and I'm applying the dynamic columnDefinition only to a specific column in a specific entity:
public class JsonColumnMappingIntegrator implements Integrator {
public static final JsonColumnMappingIntegrator INSTANCE =
new JsonColumnMappingIntegrator();
#Override
public void integrate(
Metadata metadata,
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
Database database = metadata.getDatabase();
if (PostgreSQL92Dialect.class.isAssignableFrom(database.getDialect().getClass())) {
Column acolumn=
((Column) metadata.getEntityBinding(Atable.class.getName()).getProperty("acolumn").getColumnIterator().next());
settingsCol.setSqlType("json");
}
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
}
}
metadata.getEntityBindings() would give you all Entity Bindings, over which you can iterate and then iterate over the properties. This seems quite inefficient though.
I'm also not sure whether you can set things like 'IS JSON' constraints etc., so a custom create script would be better.
I have an object which have several annotations:
#JsonInclude(Include.NON_DEFAULT) , #JsonIgnoreand#JsonProperty("BLA BLA")`
I need to serialize my object while ignoring fields that their value is not changed, and also always ignore some other fields. while serializing I want some fields to have diffrent names inside the json String. all of that works great. my problem is when i try to filter out some fields dynamically! I tried every example I could find and nothing worked, I coukdn't exclude fields dynamically. mabye my other annotations are preventing me from doing that?
This is my code:
#JsonInclude(Include.NON_DEFAULT)
public class objectFilter implements Serializable {
#JsonIgnore
private String filterDescription = "";
private String[] address = {"","0","false"};
#JsonProperty("status.statusCode")
private String[] statusCode = {"","0","false"};
#JsonIgnore
private String statusCodeDescription = "";
#JsonProperty("createdUser.userCode")
private String[] createdUser = {"","0","true"};
#JsonIgnore
private String createdUserDescription = "";
#JsonProperty("List.endorsment")
private String[] endorsment1 = {"","0","false"};
#JsonProperty("endorsment")
private String[] endorsment2 = {"","0","false"};
#JsonProperty("List.policy")
private String[] policy1 = {"","0","false"};
#JsonProperty("policy")
private String[] policy2 = {"","0","false"};
//getters and setters
}
I want to be able to exclusde some fields from being serialized even if they are not with #JsonIgnore
for example: I want exclude all fields except from policy2 and endorsment2 , even if the other fields have values inside of them.
How can this be done?
please help.
So sometimes you only want to serialize policy2 and endorsement2 while other times you want to serialize all the fields? If so you can use Jackson's JSON views to achieve this. You would first create a marker class for this view:
private interface OnlyPolicyTwoAndEndorsementTwo { }
And then you would set these two fields as belonging to this view:
..........
#JsonView(OnlyPolicyTwoAndEndorsementTwo.class)
#JsonProperty("endorsment")
private String[] endorsment2 = {"","0","false"};
..........
#JsonView(OnlyPolicyTwoAndEndorsementTwo.class)
#JsonProperty("policy")
private String[] policy2 = {"","0","false"};
And now to serialize the full object you could do:
mapper.writer().writeValueAsString(objectFilter);
Or to only serialize policy2 and endorsement2 you would do:
mapper.writerWithView(OnlyPolicyTwoAndEndorsementTwo.class).writeValueAsString(objectFilter);
Use Mixins: http://wiki.fasterxml.com/JacksonMixInAnnotations
Define that annotations of a mix-in class (or interface)
and it will be used with a target class (or interface) such that it appears
as if the target class had all annotations that the mix-in class has (for purposes of configuring serialization / deserialization)