Java and RDBMS
Married with issues
Java and RDBMS Married with issues Database constraints Speaker - - PowerPoint PPT Presentation
Java and RDBMS Married with issues Database constraints Speaker Jeroen van Schagen Situation store Java Relational Application Database retrieve JDBC JDBC J ava D ata b ase C onnectivity Data Access API ( java.sql ,
Married with issues
Jeroen van Schagen
store retrieve
Java Application Relational Database
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (‘Jan’)”; statement.executeUpdate(sql);
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (‘Jan’)”; statement.executeUpdate(sql);
User
name : varchar(3) NOT-NULL, UNIQUE
Name can only have up to 3 characters Name is required Name can only occur once
Database Maintain data
Not null Type Length Primary key Foreign key Unique key Check
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (‘Jan’)”; statement.executeUpdate(sql);
User
name : varchar(3) NOT-NULL, UNIQUE
What happens? Assuming the user table is empty
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (‘Jan’)”; statement.executeUpdate(sql);
User
name : varchar(3) NOT-NULL, UNIQUE
1 row updated
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (‘Jan’)”; statement.executeUpdate(sql); statement.executeUpdate(sql);
What will happen?
User
name : varchar(3) NOT-NULL, UNIQUE
What happens?
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (‘Jan’)”; statement.executeUpdate(sql); statement.executeUpdate(sql);
What will happen?
User
name : varchar(3) NOT-NULL, UNIQUE
SQLIntegrityConstraint ViolationException
Applicatio n JDBC Database
executeUpdate(sql) INSERT return 1 Inserted 1 executeUpdate(sql) INSERT Unique violation throw
SQLIntegrityConstraint ViolationException
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (‘Jan’)”; statement.executeUpdate(sql); statement.executeUpdate(sql);
User
name : varchar(3) NOT-NULL, UNIQUE
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (‘Jan’)”; try { statement.executeUpdate(sql); statement.executeUpdate(sql); } catch (SQLIntegrityConstraintViolationException e) { throw new RuntimeException(“Name already exists”); }
User
name : varchar(3) NOT-NULL, UNIQUE
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (NULL)”; statement.executeUpdate(sql);
User
name : varchar(3) NOT-NULL, UNIQUE
What happens?
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (NULL)”; statement.executeUpdate(sql);
User
name : varchar(3) NOT-NULL, UNIQUE
SQLIntegrityConstraint ViolationException
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (NULL)”; try { statement.executeUpdate(sql); } catch (SQLIntegrityConstraintViolationException e) { throw new RuntimeException(“Name is required”); throw new RuntimeException(“Name already exists”); }
User
name : varchar(3) NOT-NULL, UNIQUE
Unique key violation
SQLIntegrityConstraint ViolationException
Not null violation
Unique key violation Not null violation
SQLIntegrityConstraint ViolationException Which was violated?
SQLException
+ getSQLState() : int + getMessage() : String
SQLIntegrityConstraint ViolationException
SQLException
+ getSQLState() : int + getMessage() : String
SQLIntegrityConstraint ViolationException
State Name 23000 Integrity constraint 23001 Restrict violation 23502 Not null violation 23503 Foreign key violation 23505 Unique violation 23514 Check violation
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (NULL)”; try { statement.executeUpdate(sql); } catch (SQLIntegrityConstraintViolationException e) { if (e.getSQLState() == 23502) { throw new RuntimeException(“Name is required”); } else if (e.getSQLState() == 23505) { throw new RuntimeException(“Name already exists”); } }
User
name : varchar(3) NOT-NULL, UNIQUE
Connection connection = …; Statement statement = connection.createStatement(); String sql = “INSERT INTO user (name) VALUES (NULL)”; try { statement.executeUpdate(sql); } catch (SQLIntegrityConstraintViolationException e) { if (e.getSQLState() == 23502) { throw new RuntimeException(“Name is required”); } else if (e.getSQLState() == 23505) { throw new RuntimeException(“Name already exists”); } }
User
name : varchar(3) NOT-NULL, UNIQUE
Complicated Boilerplate Assumptions
Multiple not-null values
User
name : varchar(3) NOT-NULL, UNIQUE email : varchar(30) NOT-NULL, UNIQUE
uk_user_name uk_user_email Multiple not-null values Multiple unique values
User
name : varchar(3) NOT-NULL, UNIQUE email : varchar(30) NOT-NULL, UNIQUE
Which was violated?
SQLException
+ getSQLState() : int + getMessage() : String
SQLIntegrityConstraint ViolationException
HSQL
integrity constraint violation: unique constraint or index violation; UK_USER_NAME table: USER
PostgreSQL
ERROR: duplicate key value violates unique constraint \"uk_user_name\" Detail: Key (name)=(Jan) already exists.
Oracle
ORA-00001: unique constraint (GOTO.UK_USER_NAME) violated\n
MySQL
Duplicate entry 'Jan' for key 'uk_user_name'
H2
Unique index or primary key violation: "UK_USER_NAME_INDEX_1 ON GOTO.USER(NAME)"; SQL statement:\ninsert into user (name) values (?) [23505-171]
They are all difgerent
HSQL
integrity constraint violation: unique constraint or index violation; UK_USER_NAME table: USER
PostgreSQL
ERROR: duplicate key value violates unique constraint \"uk_user_name\" Detail: Key (name)=(Jan) already exists.
Oracle
ORA-00001: unique constraint (GOTO.UK_USER_NAME) violated\n
MySQL
Duplicate entry 'Jan' for key 'uk_user_name'
H2
Unique index or primary key violation: "UK_USER_NAME_INDEX_1 ON GOTO.USER(NAME)"; SQL statement:\ninsert into user (name) values (?) [23505-171]
The info is there
Just too diffjcult Focus on application logic
( for integrity constraints )
Concrete exception classes
UniqueKeyViolationException NotNullViolationException
Access to constraint info
getColumnName() getConstraintName()
if (user.getName() == null) { throw new RuntimeException(“Name is required”); }
public class User { @NotNull private String name; }
Conveys No SQL exception Less database interaction
Applicatio n Database throw new RuntimeException
User
@NotNull private String name
User
name : varchar(3) NOT-NULL, UNIQUE
Application Database
User
@NotNull private String name
User
name : varchar(3) NOT-NULL, UNIQUE
Application Database
Kept in sync Unexpected SQL exceptions
id name NULL
Testable in isolation
id name Jan
id name id name
users
Piet Henk Jan 1 2 3 Jan
Requires data
No SQL exceptions Extra query Not atomic
if (countUsersWithName(user.getName()) > 0) { throw new RuntimeException(“Name already exists”); } private int countUsersWithName(String name) { return jdbcTemplate.queryForObject( “SELECT COUNT(1) FROM user where name = ?”, name, Long.class); }
Applicatio n Database
INSERT (name) VALUES (‘Jan’) INSERTED 1
Thread 2
COUNT WHERE name = ‘Jan’ return 0
Thread 1
INSERT (name) VALUES (‘Jan’) Unique key violation
Thread 1
Decision on
Unexpected Uncaught
No SQL exceptions Error prone Duplication
Not null
Extra query
Unique key
No SQL exceptions Error prone
Lack proper solution
Java Repository Bridge - JaRB
Testable in isolation
Not null Type Length Check Unique key Foreign key Primary key
Not null Type Length
@Entity
public class User { @NotNull @Length(max=3) private String name; private String email; }
User
name : varchar(3) NOT-NULL, UNIQUE email : varchar(100)
No duplication Retrieve constraints Database as
validate(new User(‘Henk’));
varchar(3) not null
name = ‘Henk’ email = null
Database Application
user.name
Determine column name (Hibernate)
Database Application
name = ‘Henk’ email = null
varchar(3) not null
user.name
validate(new User(‘Henk’));
validate(new User(‘Henk’));
name = null email = null
validate(new User(null));
varchar(3) not null
Database Application
user.name
validate(new User(‘Henk’));
Application
name = null email = null
varchar(3) not null
validate(new User(null));
Database
user.name
validate(new User(‘Henk’));
name = ‘Jan’ email = null
validate(new User(null)); validate(new User(‘Jan’));
Database Application
varchar(3) not null
user.name
validate(new User(‘Henk’));
Database Application
name = ‘Jan’ email = null
varchar(3) not null
user.name
validate(new User(null)); validate(new User(‘Jan’));
validate(new User(‘Henk’));
name = ‘Jan’ email = null
varchar(100)
Database Application
user.email
validate(new User(null)); validate(new User(‘Jan’));
validate(new User(‘Henk’));
Database Application
name = ‘Jan’ email = null
varchar(100)
user.email
validate(new User(null)); validate(new User(‘Jan’));
validate(new User(‘Henk’));
Database Application
name = ‘Jan’ email = null
varchar(100)
user.email
validate(new User(null)); validate(new User(‘Jan’));
@MappedSuperclass
public abstract class BaseEntity { } @Entity public class User extends BaseEntity { private String name; private String email; }
public class User { private String name; private String email; }
Custom schema mapper
Check Unique key Foreign key Primary key
ConstraintViolationException getConstraintName()
Access to constraint name Hardcoded names Heavy for plain JDBC
try { // Insert user } catch (ConstraintViolationException e) { if (e.getConstraintName() == “uk_user_name”) { // Handle error } }
Too technical Focus on domain
DataAccessException DataIntegrityViolationException
Consistent hierarchy Extensible
Spring$Proxy PersistenceExceptionTranslator
ConstraintViolation Exception JPASystemException
UserRepository
No constraint name
ConstraintViolationException getConstraintName() JPASystemException DataAccessException
cause
Hierarchy Weaker API
try { userRepository.save(user); } catch (JPASystemException e) { ConstraintViolationException ce = (ConstraintViolationException) e.getCause(); if (ce.getConstraintName() == “uk_user_name”) { // Handle error } } Unsafe cast
Why isn’t this easier?
Constraint name Hierarchy Extensible Hibernate Spring JaRB Best of both worlds
Map each constraint to a custom exception.
try { userRepository.save(new User(“Jan”)); } catch (UserNameAlreadyExistsException e) { error(“User name already exists.”); }
try { userRepository.save(new User(“Jan”)); } catch (UserNameAlreadyExistsException e) { error(“User name already exists.”); } catch (UserEmailAlreadyExistsException e) { error(“User email already exists.”); }
SQLIntegrity ConstraintException UserNameAlready ExistsException
ERROR: duplicate key value violates unique constraint \"uk_user_name\" Detail: Key (name)=(Jan) already exists.
SQLIntegrity ConstraintException
ERROR: duplicate key value violates unique constraint \"uk_user_name\" Detail: Key (name)=(Jan) already exists.
Vendor specific Pattern matching
Column name Value Constraint name
Version specific
SQLIntegrity ConstraintException
UniqueKeyViolationException NotNullViolationException LengthExceededViolationExceptio n PrimaryKeyViolationException CheckFailedException InvalidTypeException ForeignKeyViolationException
DatabaseConstraintViolationException
UniqueKeyViolationException NotNullViolationException LengthExceededViolationExceptio n PrimaryKeyViolationException CheckFailedException InvalidTypeException ForeignKeyViolationException UserNameAlreadyExistsException
Constraint info
@NamedConstraint(“uk_user_name”) public class UserNameAlreadyExistsException extends UniqueKeyViolationException { }
Scanned from class path Registered on constraint
uk_user_name
UserNameAlreadyExistsException
uk_user_email
UniqueKeyViolationException
@NamedConstraint(“uk_user_name”) public class UserNameAlreadyExistsException extends UniqueKeyViolationException { UserNameAlreadyExistsException(…) { } }
DatabaseConstraintViolation Throwable (cause) ExceptionFactory
try { userRepository.save(new User(“Jan”)); } catch (UniqueKeyViolationException e) { error(“User name already exists.”); }
@EnableDatabaseConstraints(basePackage = “org.myproject”)
Enable exception translation Enable database validation Resolve database vendor Register custom exceptions
<jarb:enable-constraints base-package=“org.myproject”/>
<dependency> <groupId>org.jarbframework</groupId> <artifactId>jarb-constraints</artifactId> <version>2.1.0</version> </dependency>
Maven central
http://www.jarbframework.org
Github