Why Spring Boot/JPA creates constraints names like this fkm5pcdf557o18ra19dajf7u26a? - postgresql

I have an entity called City:
#Entity
#Table (name = "cities")
public class City implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(updatable = false, nullable = false, columnDefinition = "serial")
private Long id;
#Column(nullable = false)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false)
private Department department;
#Column(nullable = true)
private Integer code;
}
In this case if I not specify a name for the field Department it creates a field called department_id in my table "cities", that's Ok but when I see the Constraints created appears a constraint with name fkcl2xocc3mnys8b84bw2dog35e. Why this name? Does it make any sense?
This is my yaml jpa configuration:
spring:
profiles: development
datasource:
url: jdbc:postgresql://localhost:5432/lalala
username: postgres
password: postgres
sql-script-encoding: UTF-8
driver-class-name: org.postgresql.Driver
data.jpa.repositories.enabled: true
jpa:
generate-ddl: true
hibernate:
ddl-auto: update
show_sql: true
use_sql_comments: true
format_sql: true
type: trace
jdbc.lob.non_contextual_creation: true
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
jdbc.lob.non_contextual_creation: true
dialect: org.hibernate.dialect.PostgreSQLDialect

Hibernate generates a constraint name by concatenating table and properties names and convert the result to MD5. It is needed, because of the constraint names length restriction in some databases. For an example, in the Oracle database, a foreign key name length can't be more than 30 symbols length.
This code snippet from Hibernate source org.hibernate.mapping.Constraint
/**
* If a constraint is not explicitly named, this is called to generate
* a unique hash using the table and column names.
* Static so the name can be generated prior to creating the Constraint.
* They're cached, keyed by name, in multiple locations.
*
* #return String The generated name
*/
public static String generateName(String prefix, Table table, Column... columns) {
// Use a concatenation that guarantees uniqueness, even if identical names
// exist between all table and column identifiers.
StringBuilder sb = new StringBuilder( "table`" + table.getName() + "`" );
// Ensure a consistent ordering of columns, regardless of the order
// they were bound.
// Clone the list, as sometimes a set of order-dependent Column
// bindings are given.
Column[] alphabeticalColumns = columns.clone();
Arrays.sort( alphabeticalColumns, ColumnComparator.INSTANCE );
for ( Column column : alphabeticalColumns ) {
String columnName = column == null ? "" : column.getName();
sb.append( "column`" ).append( columnName ).append( "`" );
}
return prefix + hashedName( sb.toString() );
}
/**
* Hash a constraint name using MD5. Convert the MD5 digest to base 35
* (full alphanumeric), guaranteeing
* that the length of the name will always be smaller than the 30
* character identifier restriction enforced by a few dialects.
*
* #param s
* The name to be hashed.
* #return String The hased name.
*/
public static String hashedName(String s) {
try {
MessageDigest md = MessageDigest.getInstance( "MD5" );
md.reset();
md.update( s.getBytes() );
byte[] digest = md.digest();
BigInteger bigInt = new BigInteger( 1, digest );
// By converting to base 35 (full alphanumeric), we guarantee
// that the length of the name will always be smaller than the 30
// character identifier restriction enforced by a few dialects.
return bigInt.toString( 35 );
}
catch ( NoSuchAlgorithmException e ) {
throw new HibernateException( "Unable to generate a hashed Constraint name!", e );
}
}
You can generate your own constraint names (unique and foreign key) using ImplicitNamingStrategy. You can refer Hibernate5NamingStrategy , as an example.

This might be the default name generated by the provider. You can use ForeignKey annotation to specify the name
#JoinColumn(foreignKey = #ForeignKey(name = "FK_NAME"))

Related

How to convert sqlite data to room database?

we already created sqlite database. this database we stored 500 000 up data but now we want to convert those data on room database. How to stored those all data in room database?
Migrate from SQLite to Room
Example : 😎
SQLiteOpenHelper implementation
User.java
public class User {
private int uId;
private String uName;
private String uContact;
public User() {
}
public User(int id, String name, String number){
this.uId = id;
this.uName = name;
this.uContact= number;
};
//getters setters left out for brevity
}
UserDbHelper.java
public class UserDbHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
// Database Name
private static final String DATABASE_NAME = "userDB";
// user table name
private static final String TABLE_USERS = "users";
// user Table Columns names
private static final String USER_ID = "user_id";
private static final String USER_NAME = "user_name";
private static final String USER_PH_NO = "user_contact";
public UserDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
//create the table if not yet created
String CREATE_USER_TABLE = "CREATE TABLE " + TABLE_USERS + "("
+ USER_ID + " INTEGER PRIMARY KEY," + USER_NAME + " TEXT,"
+ USER_PH_NO + " TEXT" + ")";
db.execSQL(CREATE_USER_TABLE);
}
#Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
// Drop older table if existed
db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS);
// Create tables again
onCreate(db);
}
public void addUser(User user){
//Add new user to database
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(USER_NAME, user.getUName()); // Contact Name
values.put(USER_PH_NO, user.getUContact()); // Contact Phone
values.put(USER_ID, user.getUId());
// Inserting Row
db.insert(TABLE_USERS, null, values);
db.close(); // Closing database connection
}
public User getUser(int id){
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_USERS, new String[] { USER_ID,
USER_NAME, USER_PH_NO }, USER_ID + "=?",
new String[] { String.valueOf(id) }, null, null, null, null);
if (cursor != null)
cursor.moveToFirst();
User contact = new User(Integer.parseInt(cursor.getString(0)),
cursor.getString(1), cursor.getString(2));
return contact;
}
}
add data
//get database instance
UserDbHelper userDbHelper = new UserDbHelper(this);
//add couple of users
userDbHelper.addUser(new User(1,"Hulk","11-445-9999"));
userDbHelper.addUser(new User(2,"Dominic","11-445-9999"));
And fetch the users using this.
User extractedUser = userDbHelper.getUser(1);
Migrating to Room:
User.java
#Entity
public class User {
#PrimaryKey
private int uId;
private String uName;
private String uNumber;
public User() {
}
#Ignore
public User(int id, String name, String number){
this.uId = id;
this.uName = name;
this.uNumber= number;
};
//add all the getters and setters here
}
UserDAO.java
#Dao
public interface UserDAO {
#Insert(onConflict = REPLACE)
void insertUser(User user);
#Query("SELECT * FROM User")
List<User> getUsers();
}
UserDB.java
#Database (entities = {User.class},version = 2)
public abstract class UserDB extends RoomDatabase {
public abstract UserDAO userDAO();
}
Migration instance
final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
// Since we didn't alter the table, there's nothing else to do here.
}
};
//create db
final UserDB userDB = Room.databaseBuilder(this
, UserDB.class
, "userDB")
.addMigrations(MIGRATION_1_2)
.build();
//inserting and accessing data using DAO
//this operations needs to be performed on thread other than main thread
userDB.userDAO().insertUser(new User123(5,"Wowman", "888888888"));
userDB.userDAO().insertUser(new User123(6,"Captain", "888888888"));
User user = userDB.userDAO().getUsers().get(0);
Table
#ColumnInfoname(name="user_id")
private int uId;
For full demo https://github.com/amit-bhandari/RoomSamples
This is a potentially difficult task as Room has quite specific requirements for column definitions (the dreaded expected .... found ....).
In short Room can only have column types that are one off INTEGER, REAL, TEXT. BLOB. (note that Room does not support the NUMERIC type).
Furthermore column constraints also have to suit what Room expects.
I would suggest that the way to go is to create the #Entity annotated classes (one per table) with suitable members/fields, that matches the original database as closely as possible
Each #Entity annotated class MUST have a PRIMARY KEY either a single member/field annotated with #PrimaryKey or the primary key can be specified using the primaryKeys parameter of the #Entity annotation.
I would suggest after creating the first #Entity annotated class to then create the #Database annotated abstract class that extends the RoomDatabase class. Noting that the entities parameter should be a list of the #Entity annotated class (i.e. indicating which classes/tables are to be used for the database).
After the creation of each (at least at first) I would suggest compiling the project, so as to limit the possible issues that may be encountered.
Once you have all the #Entity annotated classes then you can consider the migration aspect.
As you have pre-existing data you will want to include the original database as an asset. Room can then copy this database from the asset via the createFromAsset method. However, there is a high chance that the tables will need converting to suit Room.
You can convert the tables either prior to creating the asset file or you could convert the tables via the prePackagedDatabaseCallback. I would suggest that the former is simpler as you can use one of the available SQLite Tools.
Obviously you need to know what needs to be converted. Rather to try to explain the rules. Room, after successfully compiling the project, generates the SQL that it uses to create the tables (and indexes and views)
triggers are not something that Room caters for (an exception being for FTS), so if you have triggers then you could add them as part of the conversion.
In the Android View after compiling you will see that in Java(generated) there will be class that is the same as the #Database annotated class but suffixed with Impl. In this class there will be a method createAllTables. This includes the SQL for all of the tables and indexes (ignore tables that start with sqlite_, android_ or room_ as these are system tables and will be created as required).
Basically you create these tables and then copy the data from original. Obviously one set of tables MUST have different names, after the data has been coped you then drop the original tables ending up with the new tables with the original names.
Thus at some stage you will have to rename a set of tables.
You can either
create the new tables different name, copy the data, drop the original tables and then rename the new tables, or
rename the original tables, create the new tables, copy the data and drop the renamed original tables
There is a good chance that you could do the copies using a simple INSERT SELECT. e.g. INSERT INTO new_table SELECT * FROM original_table;
however complexities can arise, again sometimes Room will have expectations that may not suit exactly.
Simple Example
This is based upon a data base with three tables:-
1) The establishment table with 5 columns:-
the establishment_id, a unique integer value that is the primary key.
the establishment_name, which has the name of the establishmnet as a text/string value and has the UNIQUE constraint.
the country, a text/string value.
the state, a text/string value.
the city a text/string value.
However, the column types use the flexibility of SQLite's column types to demonstrate (wrongly or rightfully is debatable and that debate is beyond the scope of the answer).
the DDL being
:-
CREATE TABLE establishment (
establishment_id INTEGER PRIMARY KEY,
establishment_name the_name_of_the_establishment UNIQUE,
country unusualcolumntype,
state VARCHAR(16),
city charvar(32)
);
The table is populated with according to:-
and
2) The classroom table with 3 columns:-
the classroom_id, a unique integer value that is the primary key.
the clasroom_name, a text/string value that has the UNQIUE constraint.
the classroom_capacity, an integer value.
the DDL being
:-
CREATE TABLE classroom (
classroom_id INTEGER PRIMARY KEY,
classroom_name UNIQUE,
classroom_capacity
);
the table is populated according to:-
finally
3) the establishment_classroom_map, an associative/mapping/reference (an various other terms) table that caters for a many-many relationship between establishments and rooms (albeit it unlikely that the same room would be used by multiple establishments). The table has 2 columns:-
the establishment_id_map, an integer value which MUST be an existing value in the establishment_id column of the establishment table (aka a Foreign Key).
the classroom_id_map, likewise an integer value which MUST be an existing value in the classrom_id column of the classroom table, again a Foreign Key.
the columns form a composite primary key
additionally, to add some complexity, the classroom_id_map column is also indexed.
the DDL being
:-
CREATE TABLE establishment_classroom_map (
establishment_id_map INTEGER REFERENCES establishment(establishment_id) ON DELETE CASCADE ON UPDATE CASCADE,
classroom_id_map INTEGER REFERENCES classroom(classroom_id) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (establishment_id_map,classroom_id_map)
);
CREATE INDEX idx01_establishmentclassroommap_classroom ON establishment_classroom_map (classroom_id_map);
the table is populated as per:-
Using the following query:-
SELECT
establishment.establishment_name,
establishment.city
||','||establishment.state
||','||establishment.country AS establisment_address,
classroom.classroom_name, classroom.classroom_capacity
FROM establishment
JOIN establishment_classroom_map ON establishment.establishment_id = establishment_classroom_map.establishment_id_map
JOIN classroom ON establishment_classroom_map.classroom_id_map = classroom.classroom_id
;
results in:-
This is the Original Database to be converted.
Step1 - Creating the Room #Entity annotated classes (3 of them).
1.a in Android Studio a new empty Kotlin project is created (in this case SO74298106MigratePreRoomDatabase) and the minimal Room dependencies are added via File/Project Structure/Dependencies that result in:-
implementation 'androidx.room:room-ktx:2.5.0-beta01'
implementation 'androidx.room:room-runtime:2.5.0-beta01'
kapt 'androidx.room:room-compiler:2.5.0-beta01'
1.b for brevity and convenience a single file named DBStuff.kt is created (New Kotlin Class/File)
1.c The DBStuff file is changed from nothing to :-
const val ESTABLISHMENT_TABLE_NAME = "establishment"
const val ESTABLISHMENT_TABLE_ID_COLUMN_NAME = ESTABLISHMENT_TABLE_NAME + "_id"
const val ESTABLISHMENT_TABLE_NAME_COLUMN_NAME = ESTABLISHMENT_TABLE_NAME + "_name"
const val ESTABLISHMENT_TABLE_COUNTRY_COLUMN_NAME = "country"
const val ESTABLISHMENT_TABLE_STATE_COLUMN_NAME = "state"
const val ESTABLISHMENT_TABLE_CITY_COLUMN_NAME = "city"
#Entity(
tableName = ESTABLISHMENT_TABLE_NAME,
indices = [
Index(value = [ESTABLISHMENT_TABLE_NAME_COLUMN_NAME], unique = true)
]
)
data class Establishment(
/* as integer type AND annotated with #Primary key then an alias of the rowid */
/* note in theory rowid's can exceed the capacity of an Int, hence Long used */
/* the default value of null, is interpreted by room as not to supply a value and this the value will be generated */
/* it is inefficient to use AutoGenerate = true */
#PrimaryKey
#ColumnInfo(name = ESTABLISHMENT_TABLE_ID_COLUMN_NAME)
var establishment_id: Long?=null,
/* Note the single use of type String for all the various column types in the original */
#ColumnInfo(name = ESTABLISHMENT_TABLE_NAME_COLUMN_NAME)
var name: String, /* no ? so cannot be null */
#ColumnInfo(name = ESTABLISHMENT_TABLE_COUNTRY_COLUMN_NAME)
var country: String,
#ColumnInfo(name = ESTABLISHMENT_TABLE_STATE_COLUMN_NAME)
var state: String,
#ColumnInfo(name = ESTABLISHMENT_TABLE_CITY_COLUMN_NAME)
var city: String
)
#Database(entities = [Establishment::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
}
The constants are not required but suggested as a single point for the column, table and database names.
Room will by default use the field/member/class name for the column and table names but you can change these as has been shown via the #ColumnInfo and for tablenames the #Entity annotation.
What Room doesn't cater for is the column's UNIQUE constraint. So to enforce the UNIQUEness a unique index has been specified for the establishment_name column.
Note the #Database annotated class with the Establishment class listed in the entities parameter of the #Database annotation, otherwise no use is yet made of the database. However, if at this stage the project is compiled then:-
i.e. Room has kindly let us know what it expects the exact schema to be.
i.e. how Room interprets the #Entity annotated Establishment class.
Room expects the establishment table to be exactly:-
CREATE TABLE IF NOT EXISTS `establishment` (
`establishment_id` INTEGER,
`establishment_name` TEXT NOT NULL,
`country` TEXT NOT NULL,
`state` TEXT NOT NULL,
`city` TEXT NOT NULL,
PRIMARY KEY(`establishment_id`)
)
not quite correct as establisment_id INTEGER PRIMARY KEY will be consider as the above (noting that the column level use of PRIMARY KEY cannot be used for a composite primary key).
The above does have a potential issue in that all columns have the NOT NULL constraint. If there are null values in the original data then constraint conflicts will result unless action is taken to either ignore (INSERT OR IGNORE INTO ....) or the column is converted e.g. '.... SELECT coalesce(the_column,'default_value'), .... '
as we can see the original data above does not contain nulls so this will not be an issue.
Onto the classroom table/#Entity annotated class, we add the following code:-
const val CLASSROOM_TABLE_NAME = "classroom"
const val CLASSROOM_TABLE_ID_COLUMN_NAME = CLASSROOM_TABLE_NAME + "_id"
const val CLASSROOM_TABLE_NAME_COLUMN_NAME = CLASSROOM_TABLE_NAME + "_name"
const val CLASSROOM_TABLE_CAPACITY_COLUMN_NAME = CLASSROOM_TABLE_NAME + "_capacity"
and
#Entity(
tableName = CLASSROOM_TABLE_NAME,
indices = [
Index(value = [CLASSROOM_TABLE_NAME_COLUMN_NAME], unique = true)
]
)
data class Classroom(
#PrimaryKey
#ColumnInfo(name = CLASSROOM_TABLE_ID_COLUMN_NAME)
var classroomId: Long?=null,
#ColumnInfo(name = CLASSROOM_TABLE_NAME_COLUMN_NAME)
var name: String,
#ColumnInfo(name = CLASSROOM_TABLE_CAPACITY_COLUMN_NAME)
var capacity: Int
)
and then change the #Database annotation to include the Classroom class in the entities parameter:-
#Database(entities = [Establishment::class,Classroom::class], exportSchema = false, version = 1)
and then compile and looking at the generated database class it now includes:-
_db.execSQL("CREATE TABLE IF NOT EXISTS `classroom` (`classroom_id` INTEGER, `classroom_name` TEXT NOT NULL, `classroom_capacity` INTEGER NOT NULL, PRIMARY KEY(`classroom_id`))");
_db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_classroom_classroom_name` ON `classroom` (`classroom_name`)");
onto the establishment_classroom_map** table. This is a little more complex as Room needs to be told about
the Foreign Keys and
the index on the classroom_id_map column (so access via the classroom is more efficient) and
the composite primary key
So the following code is added:-
const val ESTABLISHMENTCLASSROOMMAP_TABLE_NAME = "${ESTABLISHMENT_TABLE_NAME}_${CLASSROOM_TABLE_NAME}_map"
const val ESTABLISHMENTCLASSROOMMAP_TABLE_ESTABLISHMENTIDMAP_COLUMN_NAME = "${ESTABLISHMENT_TABLE_ID_COLUMN_NAME}_map"
const val ESTABLISHMENTCLASSROOMMAP_TABLE_CLASSROOMIDMAP_COLUMN_NAME = "${CLASSROOM_TABLE_ID_COLUMN_NAME}_map"
and
#Entity(
tableName = ESTABLISHMENTCLASSROOMMAP_TABLE_NAME,
foreignKeys = [
ForeignKey(
entity = Establishment::class,
parentColumns = [ESTABLISHMENT_TABLE_ID_COLUMN_NAME],
childColumns = [ESTABLISHMENTCLASSROOMMAP_TABLE_ESTABLISHMENTIDMAP_COLUMN_NAME],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = Classroom::class,
parentColumns = [CLASSROOM_TABLE_ID_COLUMN_NAME],
childColumns = [ESTABLISHMENTCLASSROOMMAP_TABLE_CLASSROOMIDMAP_COLUMN_NAME],
onDelete = CASCADE,
onUpdate = CASCADE
)
],
primaryKeys = [ESTABLISHMENTCLASSROOMMAP_TABLE_ESTABLISHMENTIDMAP_COLUMN_NAME, ESTABLISHMENTCLASSROOMMAP_TABLE_CLASSROOMIDMAP_COLUMN_NAME]
)
data class EstablishmentClassroomMap(
#ColumnInfo(name = ESTABLISHMENTCLASSROOMMAP_TABLE_ESTABLISHMENTIDMAP_COLUMN_NAME)
var establishment_id_map: Long,
#ColumnInfo(name = ESTABLISHMENTCLASSROOMMAP_TABLE_CLASSROOMIDMAP_COLUMN_NAME, index = true)
var classroom_id_map: Long
)
foreign keys and composite primary key are defined in the #Entity annotation
and
the index on the classroom_id_map column is defined in the #ColumnInfo annotation
After yet again amending the #Database annotation to now use entities = [Establishment::class,Classroom::class, EstablishmentClassroomMap::class] and then compiling the following is additionally generated:-
_db.execSQL("CREATE TABLE IF NOT EXISTS `establishment_classroom_map` (`establishment_id_map` INTEGER NOT NULL, `classroom_id_map` INTEGER NOT NULL, PRIMARY KEY(`establishment_id_map`, `classroom_id_map`), FOREIGN KEY(`establishment_id_map`) REFERENCES `establishment`(`establishment_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`classroom_id_map`) REFERENCES `classroom`(`classroom_id`) ON UPDATE CASCADE ON DELETE CASCADE )");
_db.execSQL("CREATE INDEX IF NOT EXISTS `index_establishment_classroom_map_classroom_id_map` ON `establishment_classroom_map` (`classroom_id_map`)");
Now the SQL for the creation of the tables that Room expects exists.
Step 2 Converting the original data to suit room.
IT IS STRONGLY SUGGESTED THAT THIS IS DONE USING A COPY OF THE ORIGINAL DATABASE OR THAT THE ORIGINAL DATABASE IS BACKED UP
This is probably best done using an SQLite tool (Navicat for SQlite used in this case). In short you generate and populate the tables created as per the generated SQl after successful compilation.
Here's the query used for the conversion (it includes the original query used above to display the joined data):-
/*
>>>>>>>>>> IMPORTANT <<<<<<<<<<
>>>>>>>>>> COMMENTED OUT AFTER FIRST RUN AS THEY WILL FAIL AFTER RUNNING ONCE <<<<<<<<<<
ALTER TABLE establishment_classroom_map RENAME TO original_establishment_classroom_map;
ALTER TABLE establishment RENAME TO original_establishment;
ALTER TABLE classroom RENAME TO original_classroom;
*/
/* FROM Generated Java (copied here as is so it can be copied from here):-
_db.execSQL("CREATE TABLE IF NOT EXISTS `establishment` (`establishment_id` INTEGER, `establishment_name` TEXT NOT NULL, `country` TEXT NOT NULL, `state` TEXT NOT NULL, `city` TEXT NOT NULL, PRIMARY KEY(`establishment_id`))");
_db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_establishment_establishment_name` ON `establishment` (`establishment_name`)");
_db.execSQL("CREATE TABLE IF NOT EXISTS `classroom` (`classroom_id` INTEGER, `classroom_name` TEXT NOT NULL, `classroom_capacity` INTEGER NOT NULL, PRIMARY KEY(`classroom_id`))");
_db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_classroom_classroom_name` ON `classroom` (`classroom_name`)");
_db.execSQL("CREATE TABLE IF NOT EXISTS `establishment_classroom_map` (`establishment_id_map` INTEGER NOT NULL, `classroom_id_map` INTEGER NOT NULL, PRIMARY KEY(`establishment_id_map`, `classroom_id_map`), FOREIGN KEY(`establishment_id_map`) REFERENCES `establishment`(`establishment_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`classroom_id_map`) REFERENCES `classroom`(`classroom_id`) ON UPDATE CASCADE ON DELETE CASCADE )");
_db.execSQL("CREATE INDEX IF NOT EXISTS `index_establishment_classroom_map_classroom_id_map` ON `establishment_classroom_map` (`classroom_id_map`)");
*/
/* Just in case the tables exist drop them (they shouldn't but if rerunning they will)*/
DROP TABLE IF EXISTS establishment_classroom_map;
DROP TABLE IF EXISTS establishment;
DROP TABLE IF EXISTS classroom;
CREATE TABLE IF NOT EXISTS `establishment` (
`establishment_id` INTEGER,
`establishment_name` TEXT NOT NULL,
`country` TEXT NOT NULL,
`state` TEXT NOT NULL,
`city` TEXT NOT NULL,
PRIMARY KEY(`establishment_id`)
);
CREATE UNIQUE INDEX IF NOT EXISTS `index_establishment_establishment_name` ON `establishment` (`establishment_name`);
CREATE TABLE IF NOT EXISTS `classroom` (
`classroom_id` INTEGER,
`classroom_name` TEXT NOT NULL,
`classroom_capacity` INTEGER NOT NULL,
PRIMARY KEY(`classroom_id`)
);
CREATE UNIQUE INDEX IF NOT EXISTS `index_classroom_classroom_name` ON `classroom` (`classroom_name`);
CREATE TABLE IF NOT EXISTS `establishment_classroom_map` (
`establishment_id_map` INTEGER NOT NULL,
`classroom_id_map` INTEGER NOT NULL,
PRIMARY KEY(
`establishment_id_map`,
`classroom_id_map`
),
FOREIGN KEY(`establishment_id_map`) REFERENCES `establishment`(`establishment_id`) ON UPDATE CASCADE ON DELETE CASCADE ,
FOREIGN KEY(`classroom_id_map`) REFERENCES `classroom`(`classroom_id`) ON UPDATE CASCADE ON DELETE CASCADE
);
INSERT /* OR IGNORE */ INTO establishment SELECT * FROM original_establishment;
INSERT /* OR IGNORE */ INTO classroom SELECT * FROM original_classroom;
INSERT /* OR IGNORE */ INTO establishment_classroom_map SELECT * FROM original_establishment_classroom_map;
/* MORE EFFICIENT IF LEFT TILL AFTER INSERTS BUT WILL NOT DETECT UNIQUE CONFLICTS */
CREATE INDEX IF NOT EXISTS `index_establishment_classroom_map_classroom_id_map` ON `establishment_classroom_map` (`classroom_id_map`);
SELECT
establishment.establishment_name,
establishment.city
||','||establishment.state
||','||establishment.country AS establisment_address,
classroom.classroom_name, classroom.classroom_capacity
FROM establishment
JOIN establishment_classroom_map ON establishment.establishment_id = establishment_classroom_map.establishment_id_map
JOIN classroom ON establishment_classroom_map.classroom_id_map = classroom.classroom_id
;
/* ONLY INTRODUCE WHEN FULLY CONFIDENT THAT THE CONVERSION WORKS
DROP TABLE IF EXISTS original_establishment_classroom_map;
DROP TABLE IF EXISTS original_establishment;
DROP TABLE IF EXISTS original_classroom;
*/
The end result (output from query) :-
Note as this is just a demo the original tables have not been dropped
The database should then be saved and closed.
Step 3 using the converted database
Copy and paste the database into the assets folder (you may have to create the assets folder, right click App/New/Directory/src/main/assets)
Create an #Dao annotated interface so that the database can be accessed easily. e.g.
#Dao
interface AllDao {
#Query("SELECT * FROM ${ESTABLISHMENT_TABLE_NAME}")
fun getAllEstablishments(): List<Establishment>
#Query("SELECT * FROM ${CLASSROOM_TABLE_NAME}")
fun getAllClassrooms(): List<Classroom>
#Query("SELECT * FROM ${ESTABLISHMENTCLASSROOMMAP_TABLE_NAME}")
fun getAllEstablishmentClassroomMaps(): List<EstablishmentClassroomMap>
}
Amend the #Database annotated class to include an abstract function to get an instance of the #Dao annotated class. You may wish to add a singleton to allow a single instance of the database to be retrieved rather than using Room's databaseBuilder method multiple times. Noting that you will be using the databaseBuilder's createFromAsset to copy the database from the assets folder when the database is first required (i.e. the very first time the App is run) so you could for example have:-
#Database(entities = [Establishment::class,Classroom::class, EstablishmentClassroomMap::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context, TheDatabase::class.java, "TheDatabase.db")
.allowMainThreadQueries()
.createFromAsset("TheDatabase.db")
.build()
}
return instance as TheDatabase
}
}
}
NOTE the string encoded in the createFromAsset this MUST match the file name of the copied database (which can be renamed to suit) e.g:-
Lastly to test some code is added in an activity to access the database (the copy from the assets folder will only be done if an attempt is made to actually access the database (getting an instance of the #Database annotated class WILL NOT access the database)) so for example:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
for (e in dao.getAllEstablishments()) {
Log.d("DBINFO_ESTAB","ID is ${e.establishment_id} name is ${e.name} etc")
}
for (c in dao.getAllClassrooms()) {
Log.d("DBINFO_CLS","ID is ${c.classroomId} name is ${c.name} etc")
}
for (ecm in dao.getAllEstablishmentClassroomMaps()) {
Log.d("DBINFO_ECM", "ESTID is ${ecm.establishment_id_map} CLSID is ${ecm.classroom_id_map}")
}
}
}
And when run the log contains:-
2022-11-04 14:04:22.436 D/DBINFO_ESTAB: ID is 100 name is Massachusetts Institute of Technology (MIT) etc
2022-11-04 14:04:22.436 D/DBINFO_ESTAB: ID is 110 name is Oxford University etc
2022-11-04 14:04:22.436 D/DBINFO_ESTAB: ID is 120 name is Cambridge University etc
2022-11-04 14:04:22.436 D/DBINFO_ESTAB: ID is 130 name is Hull University etc
2022-11-04 14:04:22.436 D/DBINFO_ESTAB: ID is 131 name is Imperial College etc
2022-11-04 14:04:22.439 D/DBINFO_CLS: ID is 200 name is RM001 etc
2022-11-04 14:04:22.439 D/DBINFO_CLS: ID is 210 name is RM002 etc
2022-11-04 14:04:22.439 D/DBINFO_CLS: ID is 220 name is LAB001 etc
2022-11-04 14:04:22.439 D/DBINFO_CLS: ID is 230 name is LAB002 etc
2022-11-04 14:04:22.441 D/DBINFO_ECM: ESTID is 100 CLSID is 200
2022-11-04 14:04:22.441 D/DBINFO_ECM: ESTID is 100 CLSID is 220
2022-11-04 14:04:22.441 D/DBINFO_ECM: ESTID is 110 CLSID is 210
2022-11-04 14:04:22.441 D/DBINFO_ECM: ESTID is 110 CLSID is 230
2022-11-04 14:04:22.441 D/DBINFO_ECM: ESTID is 120 CLSID is 220
2022-11-04 14:04:22.441 D/DBINFO_ECM: ESTID is 120 CLSID is 230
2022-11-04 14:04:22.441 D/DBINFO_ECM: ESTID is 130 CLSID is 200
2022-11-04 14:04:22.441 D/DBINFO_ECM: ESTID is 130 CLSID is 210
Using App inspection and the query from the conversion:-

JPA generate partial unique key

I have a table with 3 columns
UUID - A UUID that is the primary key of the table
ID - A human readable ID of the resource (for a new resource, the ID should be automatically generated by a sequence)
Version - A version number
I am using JPA.
The table can contain multiple records with the same "human readable" ID and different versions.
I would like to be able to insert a new record without specifying the ID: the database should generate the ID automatically.
At the same time, when I need to insert a new version of the same resource, I would like to be able to insert a new row specifying the ID.
I have created a table where the UUID is the primary key, ID is defined as "integer generated by default as identity" and version is just an integer.
Using SQL query I can do what I want, but I do not know how to do it using JPA.
If I define the column as:
#Column(name="ID", insertable = false, updatable = false, nullable = false)
I can insert new records but the ID is always generated as new even if the resource already has one because the insert does not include the column.
If I define the column as:
#Column(name="ID", insertable = true, updatable = false, nullable = false)
The insert include the column and I am able to insert new rows specifying the ID but I cannot insert a row without the ID because the SQL generated is passing a null value for that column.
UPDATE
I have modified the configuration adding the annotation #Generated:
#Generated(value = GenerationTime.INSERT)
#Column(name="ID", updatable = false, nullable = false)
private Integer id;
With this, I am having the same problem: if I pass a value for id, the database is still generating a new one.
You can try to use #DynamicInsert annotation.
Assuming that you have the following table:
create table TST_MY_DATA
(
dt_id uuid,
dt_auto_id integer generated by default as identity,
dt_version integer,
primary key(dt_id)
);
Appropriate entity will look like this:
#Entity
#Table(name = "TST_MY_DATA")
#DynamicInsert
public class TestData
{
#Id
#GeneratedValue
#Column(name = "dt_id")
private UUID id;
// Unfortunately you cannot use #Generated annotation here,
// otherwise this column will be always absent in hibernate generated insert query
// #Generated(value = GenerationTime.INSERT)
#Column(name = "dt_auto_id")
private Long humanReadableId;
#Version
#Column(name = "dt_version")
private Long version;
// getters/setters
}
and then you can persist entities:
TestData test1 = new TestData();
session.persist(test1);
TestData test2 = new TestData();
test2.setHumanReadableId(27L);
session.persist(test2);
session.flush();
// here test1.getHumanReadableId() is null
/*
* You can use session.refresh(entity) only after session.flush() otherwise you will have:
* org.hibernate.UnresolvableObjectException: No row with the given identifier exists:
* [this instance does not yet exist as a row in the database#ff09c202-cd17-4d4a-baea-057e475fabb9]
**/
session.refresh(test1);
// here you can use the test1.getHumanReadableId() value fetched from DB

Composite Key and mapping in grails 2.2.5 with legacy database

I have 4 tables. osiguranje_paket, atribut, tip_unosa, razna_polja. osiguranje_paket, atribut, tip_unosa are Parents of razna_polja table.
razna_polja table has composite key that is consisted from two primary keys (osgp_id = osiguranje_paket table + atr_id = atribut table). The relationships between them are one-to-many bidirectional and I'm using Legacy PostgreSQL Database with dynamic scaffolding, I can not make any changes to database or tables or anything. How can I map my classes to use composite key, what do I need to add or change in my domains? Any help would be appreciated.
CREATE TABLE revoco.osiguranje_paket
(
osgp_id serial NOT NULL,
osg_id integer NOT NULL,
osgp_napomena character varying(500),
tpo_id integer NOT NULL,
osgp_link character varying(155),
osgp_oznaka character varying(10),
CONSTRAINT osgp_pk PRIMARY KEY (osgp_id),
CONSTRAINT osg_osgp_fk FOREIGN KEY (osg_id)
REFERENCES revoco.osiguranje (osg_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT tpo_osgp_fk FOREIGN KEY (tpo_id)
REFERENCES revoco.tip_osiguranja (tpo_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE revoco.atribut
(
atr_id serial NOT NULL,
atr_naziv character varying(155) NOT NULL,
lab_id integer,
atr_rbr integer,
CONSTRAINT atr_pk PRIMARY KEY (atr_id),
CONSTRAINT atr_lab_labela_fk FOREIGN KEY (lab_id)
REFERENCES common.labela (lab_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
CREATE TABLE common.tip_unosa
(
tpu_id serial NOT NULL,
tpu_val character varying(32) NOT NULL,
CONSTRAINT tpu_pk PRIMARY KEY (tpu_id),
CONSTRAINT tpu_vrijednost_unique UNIQUE (tpu_val)
)
CREATE TABLE common.razna_polja
(
osgp_id integer NOT NULL,
atr_id integer NOT NULL,
tpu_id integer NOT NULL,
rap_odjel integer NOT NULL DEFAULT 0,
rap_vidljiv boolean NOT NULL DEFAULT true,
CONSTRAINT rap_pk PRIMARY KEY (osgp_id, atr_id),
CONSTRAINT rap_atr_atribut_fk FOREIGN KEY (atr_id)
REFERENCES revoco.atribut (atr_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT rap_osgp_paket_fk FOREIGN KEY (osgp_id)
REFERENCES revoco.osiguranje_paket (osgp_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT rap_tpu_tip_unosa_fk FOREIGN KEY (tpu_id)
REFERENCES common.tip_unosa (tpu_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT rap_ispravan_odjel_ck CHECK (rap_odjel >= 0 AND rap_odjel <= 1)
)
This are my Domain classes
OsiguranjePaket.groovy
import common.RaznaPolja
class OsiguranjePaket {
Integer id
String osgp_napomena
String osgp_link
String osgp_oznaka
static belongsTo = [osg: Osiguranje, tpo: TipOsiguranja]
static hasMany = [raznaPolja: RaznaPolja]
String toString(){
"${osgp_oznaka}"
}
static fetchMode = [raznapolja: 'eager']
static constraints = {
id(unique: true)
osgp_link (nullable: true, blank: false, size: 0..155)
osgp_napomena (nullable: true, blank: false, size: 0..500)
osgp_oznaka (nullable: true, blank: false, size: 0..10)
}
static mapping = {
table name: 'osiguranje_paket', schema: 'revoco'
version false
id generator :'identity', column :'osgp_id', type:'integer'
}
}
Atribut.groovy
import common.RaznaPolja
import common.Labela
class Atribut {
Integer id
String atr_naziv
Integer atr_rbr
static hasMany = [raznaPolja: RaznaPolja]
static belongsTo = [lab: Labela]
static fetchMode = [raznaPolja: 'eager']
String toString(){
"${atr_naziv}"
}
static mapping = {
table name: "atribut", schema: "revoco"
version false
id generator :'native', column :'atr_id'
}
static constraints = {
id(blank: false, unique: true)
atr_naziv (blank: false, size: 0..155)
atr_rbr (nullable: true)
}
}
TipUnosa.groovy
class TipUnosa {
Integer id
String tpu_val
static hasMany = [raznaPolja: RaznaPolja]
static fetchMode = [raznaPolja: 'eager']
String toString(){
"${tpu_val}"
}
static constraints = {
id (blank:false, size: 0..10)
tpu_val (blank:false, unique:true, size:0..32)
}
static mapping = {
table name: "tip_unosa", schema: "common"
version false
id generator :'identity', column :'tpu_id', type:'integer'
}
}
RaznaPolja.groovy
import java.io.Serializable;
import revoco.Atribut
import revoco.OsiguranjePaket
class RaznaPolja implements Serializable {
Integer rap_odjel
Boolean rap_vidljiv
//without this getting common.RaznaPolja(unsaved)
String toString(){
"${id}" //Getting null
}
static belongsTo = [atr: Atribut, osgp: OsiguranjePaket, tpu: TipUnosa]
static mapping = {
table name: 'razna_polja', schema: 'common'
id composite: ['osgp', 'atr']
// cache usage:'read-only'
version false
rap_odjel column: 'rap_odjel', type: 'integer'
rap_vidljiv column:'rap_vidljiv', type: 'boolean'
}
}
Composite key
To set a composite key you need to use the mapping property of your domain class and your domain class must implement the Serializable interface. Here's an example from the Grails documentation.
import org.apache.commons.lang.builder.HashCodeBuilder
class Person implements Serializable {
String firstName
String lastName
boolean equals(other) {
if (!(other instanceof Person)) {
return false
}
other.firstName == firstName && other.lastName == lastName
}
int hashCode() {
def builder = new HashCodeBuilder()
builder.append firstName
builder.append lastName
builder.toHashCode()
}
static mapping = {
id composite: ['firstName', 'lastName']
}
}
Database mapping
The domain class mapping property is also used to change the database table and columns your domain class maps to as you can see here.
Associations
As for the relationships between the tables, the documentation can give you some clues. You may have to add some mapping domain classes here and there to create what you need, but the Grails associations should be able to handle your needs.

Using CriteriaBuilder to select entities referenced by refTable

I'm very new to JPA (based on eclipselink) so be patient with me and my question. I found JPA is an elegant way to handle simple DB statements. But, now I'd like to do some more complicated task.
So let me explain what I want: I've got a table holding person data (lets say the persons name). In an other table I'd like to keep address lists. Because a person way part of several adresslists and one adress list may keep several persons I need an ref-table jo join these collections.
So my DB-tables look like this:
SQL (not exact DDL syntax but it helps to understand!) --without constraints etc
--the person keeps data of persons
CREATE TABLE a_person
(
oid serial PrimaryKey NOT NULL,
vorname character varying(50),
nachname character varying(50) NOT NULL
--
)
--the adresslist is quite simple: just a name of the list is storred
CREATE TABLE a_adressliste (
(
oid serial PrimaryKey NOT NULL,
label character varying(25) NOT NULL
--<more collumns here>
)
--the table refering the list the the containing persons
CREATE TABLE a_listen_person
(
fk_adressliste bigint PrimaryKey NOT NULL, --ref to a_person.oid
fk_person bigint PrimaryKey NOT NULL, --ref to a_adressliste.oid
)
Having this structure and some data in the DB it's easy to select the adress lists using the following SQL statement:
select * from a_person p where p.oid in (
select
lp.fk_person
from
a_listen_person lp, a_adresssliste al
where
lp.fk_adressliste = al.oid AND al.label = {LISTE_LABEL}
)
Now, According tho the structure within the DB, I've got the corresponding JAVA POJO's to these tables (I've skipped the annotations to keep my code a little shorter)
JAVA
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "oid")
private Integer oid;
private String vornmae;
private String nachname;
//getter; setter and the other missing stuff...
}
public class Adressliste implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "oid")
private Integer oid;
private String label;
}
public class ListenPerson implements Serializable {
#EmbeddedId
private ListenPersonPK listenPerson;//build from Person.oid and Adressliste.oid
private Person person;
private Adressliste adresssliste;
//getter; setter and the other missing stuff...
}
Now I've written a finder method in JAVA where I use a CriteriaBuilder to filter the entities by several attributes (according to the Person POJO). But I did not manage to select the Person according to a given list name.
By now my method looks like this:
public TypedQuery<Person> prepareQueryFiltered(Filter filter) {
TypedQuery<Person> retVal;
EntityManager em = this.getEntityManager();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> query = builder.createQuery(Person.class);
Root<Person> cust = query.from(Person.class);
query.select(cust);
List<Predicate> predicateList = new ArrayList<Predicate>();
Predicate predicate;
if (null != filter.getVorname()) {
predicate = builder.like(
builder.lower(cust.<String>get("vorname")),
"%" + filter.getVorname().toLowerCase() + "%");
predicateList.add(predicate);
}
if (null != filter.getNachname()) {
predicate = builder.like(
builder.lower(cust.<String>get("nachname")),
"%" + filter.getNachname().toLowerCase() + "%");
predicateList.add(predicate);
}
//some more filtered attributes ...
query.where(predicateList.toArray(new Predicate[predicateList.size()]));
retVal = em.createQuery(query);
return retVal;
}
As you can imagine the filter attribute keeps all the data to filter my entities with. But how does the code look like if I'd like to get all the person entities belonging to a given list name?
I started to use 'Subquery's but I did not get the correct syntax. Could you please give me some hints?
For examples of sub-queries in the Criteria API see,
http://en.wikibooks.org/wiki/Java_Persistence/Criteria#Subquery
Also, I don't think you need a sub-query, a simple join should work.
Select p from Person p, ListenPerson l where l.person = p and l.adresssliste.label = :label

Java EE 6 JPA 2 ManyToOne Relation Creates Invalid Foreign Key

I am trying to create two entities where both entities have embeddedIds. One of the entities have 2 references to the other entity, where both of those references are related as ManyToOne.
Example codes are written below;
#Embeddable
public class ItemPK {
#Column(nullable = false, length = 100)
private String itemId;
#Column(name = "item_client_id", nullable = false)
private int clientId;
...
}
#Entity
#Table(name = "item")
public class Item {
#EmbeddedId
private ItemPK id;
#ManyToOne
#JoinColumn(name = "item_client_id")
private Client client;
#OneToMany(mappedBy="item", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<RelatedItem> relatedItems;
#OneToMany(mappedBy="relatedItem", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<RelatedItem> relatedItemsRHS;
...
}
#Embeddable
public class RelatedItemPK {
#Column(name = "itemId", length = 100, nullable = false)
private String itemId;
#Column(name = "item_client_id", nullable = false)
private int clientId;
#Column(name = "relatedItemId", length = 100, nullable = false)
private String relatedItemId;
#Column(name = "related_item_client_id", nullable = false)
private int relatedItemClientId;
...
}
#Entity
#Table(name = "related_item")
public class RelatedItem {
#EmbeddedId
private RelatedItemPK id;
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumns({
#JoinColumn(name="itemId", referencedColumnName="itemId", insertable=false, updatable=false),
#JoinColumn(name="item_client_id", referencedColumnName="item_client_id", insertable=false, updatable=false)
})
private Item item;
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumns({
#JoinColumn(name="related_item_client_id", referencedColumnName="item_client_id", insertable=false, updatable=false),
#JoinColumn(name="relatedItemId", referencedColumnName="itemId", insertable=false, updatable=false)
})
private Item relatedItem;
...
}
The problem is while creating foreign keys for RelatedItem entity, I got an SQLException. It is the second ManyToOne relation that fails. The foreign key generation sql is below,
ALTER TABLE related_item ADD CONSTRAINT FK_related_item_related_item_client_id FOREIGN KEY (related_item_client_id, relatedItemId) REFERENCES item (item_client_id, itemId)
Since item table is indexed first by itemId then by item_client_id, this statement causes MySQL to produce an error.
I would like to switch the places of columns so that the SQL should look like the following,
ALTER TABLE related_item ADD CONSTRAINT FK_related_item_relatedItemId FOREIGN KEY (relatedItemId, related_item_client_id) REFERENCES item (itemId,item_client_id)
I tried changing the order of "JoinColumn"s but the result didn't change. I also tried renaming the fields to check if persistence provider choses the order by column name but again the result didn't change.
So, is there a way to enforce the column ordering?
p.s. I use following stuff:
MySQL 5.1
EclipseLink 2.0.0
Java EE 6
JPA 2
GlassFish v3
Edit: EclipseLink produces following SQL, which fails to run;
CREATE TABLE related_item (SIMILARITY DOUBLE, widget_id INTEGER NOT NULL, relatedItemId VARCHAR(100) NOT NULL, itemId VARCHAR(100) NOT NULL, related_item_client_id INTEGER NOT NULL, item_client_id INTEGER NOT NULL, PRIMARY KEY (widget_id, relatedItemId, itemId, related_item_client_id, item_client_id));
CREATE TABLE item (IMAGEURL VARCHAR(2048), STATUS VARCHAR(64), URL VARCHAR(2048), PRICE DOUBLE, STOCK INTEGER, DESCRIPTION TEXT(64000), NAME VARCHAR(255), ITEMID VARCHAR(100) NOT NULL, item_client_id INTEGER NOT NULL, PRIMARY KEY (ITEMID, item_client_id));
ALTER TABLE related_item ADD CONSTRAINT FK_related_item_itemId FOREIGN KEY (itemId, item_client_id) REFERENCES item (itemId, item_client_id);
ALTER TABLE related_item ADD CONSTRAINT FK_related_item_related_item_client_id FOREIGN KEY (related_item_client_id, relatedItemId) REFERENCES item (item_client_id, itemId);
ALTER TABLE item ADD CONSTRAINT FK_item_item_client_id FOREIGN KEY (item_client_id) REFERENCES client (ID);
Please include the stack trace. However, I strongly recommend you skip the #JoinColumn tags unless you have a VERY good reason for specifying the foreign keys yourself. By specifying the mappedBy attribute in one of the directions, JPA can figure out what to do by itself.
Java EE 6 and JPA put a lot of effort into enabling Convention over Configuration, which means that most of the time, things will work out of the box. It's desirable for you, the programmer because you have less boiler plate code to worry about, and it's desirable for the JPA and Jave EE container implementors because it gives them freedom to chose the best performing solutions. By declaring the foreign key relationships yourself, you rob both you and JPA of this advantage.
Edit: In fact, I suspect that both specifying mappedBy and specifying the #JoinTable could be the root cause of your problem. But I need to see the stack trace to tell for sure.
The order of the columns should not matter. If it does, then you could change the order in your index to match, or change the order you list your primary key in, or just use your scripts to generate your DDL.