Did you know that database classes, that require many lines of Java and SQL code, may be replaced with a single line of Java 8 code? In this tutorial session you will learn how to use standard Java 8 Streams as an alternative to traditional Object Relational Mappers (ORM). We will use the open-source tool Speedment to show how development speed can be increased and how the application code can be more concise and run faster.
How to generate customized java 8 code from your database
1. How to Generate Customized Java 8 Code
From Your Database
Per Minborg
CTO, Speedment, Inc
Emil Forslund
Developer, Speedment, Inc.
2. Every Decision a Developer Makes is a
Trade-off
The best code is
no code at all
3. Using Code Generation
⢠Makes the code efficient
and short
⢠Modifications are done
once and applied
everywhere
⢠Minimizes errors
⢠âDRYâ (Donât Repeat
Yourself) vs. âWETâ (We
Enjoy Typing)
⢠âCode your codeâ
But how can we control the generated code?
4. About Us
Per Minborg
⢠Founder of several IT companies
⢠Lives in Palo Alto
⢠20 years of Java experience
⢠15+ US patents
⢠Speaker at Java events
⢠Blog: Minborgâs Java Pot
Emil Forslund
⢠Java Developer
⢠Lives in Palo Alto
⢠8 years of Java
experience
⢠Speaker at Java events
⢠Blog: Age of Java
Spire
⢠Speedment Open Source mascot
⢠Lives on GitHub
⢠2 years of mascot experience
5. Agenda
⢠Problem Description
⢠Code Generation in Database Applications
⢠How Does it Work?
⢠Code Example
⢠Controlling the Code Generation
⢠Generate Custom Classes
⢠Add new Method to Existing Classes
⢠Code Example
⢠Additional Features
⢠Beyond Open Source
⢠Questions & Answers
6. Agenda
⢠Problem Description
⢠Code Generation in Database Applications
⢠How Does it Work?
⢠Code Example
⢠Controlling the Code Generation
⢠Generate Custom Classes
⢠Add new Method to Existing Classes
⢠Code Example
⢠Additional Features
⢠Beyond Open Source
⢠Questions & Answers
7. Do You Recognize This Code?
Class.forName("org.postgresql.Driver");
try (final Connection conn = DriverManager.getConnection(
"jdbc:postgresql://hostname:port/dbname",
"username",
"password")) {
// Database Logic Here...
}
8. Why Creating DB-Apps is So Time
Consuming
⢠Even trivial database operations require a
lot of boilerplate code
⢠Mixing SQL and Java is error-prone
⢠ORMs require you to write annotated
POJOs for every table
⢠Creating even a simple DB app can take
hours
9. Open-Source Project Speedment
⢠Stream ORM Java toolkit and
runtime
⢠Generate domain-model from the
database
⢠No need for complicated
configurations or setup
⢠All operations are type-safe
⢠Data is accessed using Java 8
Streams
⢠Business friendly Apache 2-
license
14. Agenda
⢠Problem Description
⢠Code Generation in Database Applications
⢠How Does it Work?
⢠Code Example
⢠Controlling the Code Generation
⢠Generate Custom Classes
⢠Add new Method to Existing Classes
⢠Code Example
⢠Additional Features
⢠Beyond Open Source
⢠Questions & Answers
15. So How Do the Generated Code Work?
⢠Code is organized based on database structure
⢠Hash-sums make sure user changes are not overwritten
If the DB structure changes, the code is
updated with the press of a button
16. Entities, Managers and Applications
⢠An Entity represents a row in a table
⢠Is a POJO
⢠Customer
⢠CustomerImpl
⢠A Manager represents a table
⢠Responsible for the CRUD operations
⢠CustomerManager
⢠CustomerManagerImpl
⢠An Application represents the entire project
⢠Responsible for configuration and settings
⢠SalesApplication
⢠SalesApplicationBuilder
17. Querying the Database using Streams
⢠Queries are expressed using
the standard Java 8 Stream
API
⢠Streams are analyzed to
produce high-performance
queries
18. Expressing Queries as Streams
customers.stream()
.filter(Customer.REGION.equal(Region.NORTH_AMERICA))
.filter(Customer.REGISTERED.greaterOrEqual(startOfYear))
.count();
Standard Stream
API
Generated Enum
Constants
Only 1 value is loaded from
DB
Full Type-Safety
SELECT COUNT('id') FROM 'customer'
WHERE 'customer'.'region' = âNorth Americaâ
AND 'customer'.'registered' >= â2016-01-01â;
19. Querying the Database using Streams
SELECT * FROM 'customer'
REGION.equal(NORTH_AMERICA)
REGISTERED.greaterOrEqual(2016-01-01)
count()
Sourc
e
Filter
Filter
Term.
Pipeline
20. Querying the Database using Streams
SELECT * FROM 'customer'
WHERE 'customer'.'region' = âNorth Americaâ
REGION.equal(NORTH_AMERICA)
REGISTERED.greaterOrEqual(2016-01-01)
count()
Sourc
e
Filter
Filter
Term.
Pipeline
21. Querying the Database using Streams
SELECT * FROM 'customer'
WHERE 'customer'.'region' = âNorth Americaâ
AND 'customer'.'registered' >= â2016-01-01â;
REGISTERED.greaterOrEqual(2016-01-01)
count()
Sourc
e
Filter
Term.
Pipeline
22. Querying the Database using Streams
SELECT COUNT('id') FROM 'customer'
WHERE 'customer'.'region' = âNorth Americaâ
AND 'customer'.'registered' >= â2016-01-01â;
count()
Sourc
e
Term.
Pipeline
23. Querying the Database using Streams
SELECT COUNT('id') FROM 'customer'
WHERE 'customer'.'region' = âNorth Americaâ
AND 'customer'.'registered' >= â2016-01-01â;
Sourc
e
Pipeline
24. Expressing Queries as Streams
// Gets the second page of customers in North America
// sorted by name in the form of a JSON array
customers.stream()
.filter(REGION.equal(Region.NORTH_AMERICA))
.sorted(NAME.comparator())
.skip(10)
.limit(10) // JVM from hereâŚ
.collect(toJson(encode.allOf(customers)))
[
{âidâ:11, ânameâ: âŚ},
{âŚ},
âŚ
]
25. Expressing Queries as Streams
// Supports parallelism on custom executors
// with full control of thread work item layout
customers.stream()
.parallel()
.filter(REGION.equal(Region.NORTH_AMERICA))
.forEach(expensiveOperatation());
27. Step 1: Getting Speedment using Maven
<plugin>
<groupId>com.speedment</groupId>
<artifactId>speedment-maven-plugin</artifactId>
<version>3.0.3</version>
</plugin>
Generate source files based on database
28. Step 2: Initializing Speedment
SalesApplication app = new SalesApplicationBuilder()
.withPassword("qwerty")
.build();
CustomerManager customers = app.getOrThrow(CustomerManager.class);
These classes are generated
automatically
Instance is configured using Builder-
pattern
A manager class is generated for every database table
29. Step 3: Querying
Region fromWhere = Region.NORTH_AMERICA;
Instant fromWhen = Instant.parse("2016-01-01");
long count = customers.stream()
.filter(Customer.REGION.equal(fromWhere))
.filter(Customer.REGISTERED.greaterOrEqual(fromWhen))
.count();
33. Agenda
⢠Problem Description
⢠Code Generation in Database Applications
⢠How Does it Work?
⢠Code Example
⢠Controlling the Code Generation
⢠Generate Custom Classes
⢠Add new Method to Existing Classes
⢠Code Example
⢠Additional Features
⢠Beyond Open Source
⢠Questions & Answers
34. Controlling the Code Generation
⢠So far we have queried a
database with streams
⢠We have used code generation
to create entities and managers
⢠Can it be used for more?
35. What is Available out of the Box?
⢠MVC oriented code generation
⢠Modular design
⢠Database domain model
⢠JSON configuration (DSL)
⢠Java language namer
⢠Translator and TranslatorDecorator
⢠Maven goals
⢠Type mappers
36. MVC Oriented Code Generation
⢠Model
⢠File, Class, Interface, Enum, Field, Method, Constructor,
Type, Generic, Annotation, âŚ
⢠View
⢠Renders a model to Java (or another language)
⢠Set code style using custom views
⢠Control
⢠AutoImport, AutoEquals, AutoJavadoc, SetGetAdd, FinalParameters
⢠Write custom controllers to automate recurring tasks
37. MVC Oriented Code Generation
⢠Separation of concerns
⢠Code generation is type safe
⢠Catch errors compile time
⢠Discover methods directly in the IDE
⢠Reuse code segments and controllers
41. Java Language Namer
⢠Camel caser: converts from âsome_db_nameâ to
âsomeDbNameâ
⢠Java naming conventions: user, User and USER
⢠Detects keywords like âfinalâ,âstaticâ and escapes them
⢠Detects collisions
⢠Pluralizer: bag â bags, entity â entities, fish â fish
42. Translator and TranslatorDecorator
⢠Translator
⢠Renders a DB entity like a Table to a new Class or an Interface
⢠TranslatorDecorator
⢠Modifies an existing Class or Interface
43. Maven Goals
⢠speedment:tool
⢠Launches the graphical tool
⢠Allows customization of configuration model
⢠Code generation
⢠speedment:generate
⢠Code generation without launching the tool
⢠speedment:reload
⢠Reloads database metadata without launching the tool
⢠speedment:clear
⢠Removes all the generated classes (except manual changes) without launching the
tool
44. Type Mappers
⢠Controls how columns are implemented
⢠Runtime conversion between Database and Java types
java.sql.Timestamp long
45. Generation vs. Templates
⢠Separation of concerns
⢠Easily change code style
⢠Minimize maintenance
⢠Maximize reusability
46. Generate a New Custom Class
1. Create a new Translator
2. Model how the new class
should look
3. Define a Plugin Class
4. Include it in project pom.xml
Example: Generate a Point class
47. Step 1: Create a New Translator Class
public class PointTranslator extends
AbstractJavaClassTranslator<Project, Class> {
public final static TranslatorKey<Project, Class> POINT_KEY =
new TranslatorKey.of(âgenerated_point", Class.class);
public PointTranslator(Project document) { super(document, Class::of); }
@Override
protected Class makeCodeGenModel(File file) {
return newBuilder(file, getClassOrInterfaceName())
.forEveryProject((clazz, project) -> {
// Generate Content Here
}).build();
}
@Override
protected String getClassOrInterfaceName() { return âPoint"; }
@Override
protected String getJavadocRepresentText() { return "A 2-dimensional coordinate."; }
}
Every translator is identified by a
TranslatorKey
Name of generated class
Javadoc
Called every time the translator is invoked
forEvery(Project|Dbms|Schema|Table|Column|
âŚ)
49. Step 3: Define a Plugin Class
public class PointPlugin {
@ExecuteBefore(RESOLVED)
void install(CodeGenerationComponent codeGen) {
codeGen.put(
Project.class,
PointTranslator.POINT_KEY,
PointTranslator::new
);
}
}
The key defined earlier
Will execute when Speedment is
being initialized
How the translator is constructed
50. Step 4: Include it in Project pom.xml
<plugin>
<groupId>com.speedment</groupId>
<artifactId>speedment-maven-plugin</artifactId>
<version>3.0.3</version>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>point-plugin</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
<configuration>
<components>
<component>com.example.pointplugin.PointPlugin</component>
</components>
</configuration>
</plugin>
This tells Speedment to load the plugin
Make sure our plugin project is on the classpath
51. Execute
/**
* A 2-dimensional coordinate.
* <p>
* This file is safe to edit. It will not be overwritten by
the code generator.
*
* @author company
*/
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
The following file is generated: public int hashCode() {
int hashCode = 31;
hashCode += 41 * x;
hashCode += 41 * y;
return hashCode;
}
public Boolean equals(Object other) {
if (this == other) return true;
else if (other == null) return false;
else if (!(other instanceof Point)) {
return false;
}
final Point point = (Point) other;
return x == point.x && y == point.y;
}
public String toString() {
return new StringBuilder(âPoint{â)
.append(âx: â).append(x).append(â, â)
.append(ây: â).append(y).append(â}â);
}
}
52. A More Concrete Example
1. Create a new Translator
2. Model how the new class
should look
3. Define a Plugin Class
4. Include it in project pom.xml
Example: Generate an Enum of
tables in the database
53. Step 1: Create a New Translator Class
public class TableEnumTranslator extends
AbstractJavaClassTranslator<Project, Enum> {
public final static TranslatorKey<Project, Enum> TABLES_ENUM_KEY =
new TranslatorKey.of(âtables_enum", Enum.class);
public TableEnumTranslator(Project document) { super(document, Enum::of); }
@Override
protected Enum makeCodeGenModel(File file) {
return newBuilder(file, getClassOrInterfaceName())
.forEveryProject((clazz, project) -> {
// Generate Content Here
}).build();
}
@Override
protected String getClassOrInterfaceName() { return âTables"; }
@Override
protected String getJavadocRepresentText() { return "An enumeration of tables in the database."; }
}
Every translator is identified by a
TranslatorKey
Name of generated class
Javadoc
Called every time the translator is invoked
forEvery(Project|Dbms|Schema|Table|Column|
âŚ)
55. Step 3: Define a Plugin Class
public class TableEnumPlugin {
@ExecuteBefore(RESOLVED)
protected void install(CodeGenerationComponent codeGen) {
codeGen.put(
Project.class,
TableEnumTranslator.TABLES_ENUM_KEY,
TableEnumTranslator::new
);
}
}
56. Step 4: Include it in Project pom.xml
<plugin>
<groupId>com.speedment</groupId>
<artifactId>speedment-maven-plugin</artifactId>
<version>3.0.3</version>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>table-enum-plugin</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
<configuration>
<components>
<component>com.example.tableenumplugin.TableEnumPlugin</component>
</components>
</configuration>
</plugin>
This tells Speedment to load the plugin
Make sure our plugin project is on the
classpath
57. Execute
/**
* An enumeration of tables in the database.
* <p>
* This file is safe to edit. It will not be overwritten by the code generator.
*
* @author company
*/
enum Tables {
CITY, SALESPERSON;
}
The following file is generated:
58. Generate all the Things
⢠Gson Adapters
⢠Spring Configuration Files
⢠REST Controllers
⢠and much moreâŚ
60. Agenda
⢠Problem Description
⢠Code Generation in Database Applications
⢠How Does it Work?
⢠Code Example
⢠Controlling the Code Generation
⢠Generate Custom Classes
⢠Add new Method to Existing Classes
⢠Code Example
⢠Additional Features
⢠Beyond Open Source
⢠Questions & Answers
61. Add New Method to Existing Classes
⢠So far we have
⢠Queried a database with generated
classes
⢠Generated a custom class
⢠How do we modify the existing
generation of classes?
62. Add New Method to Existing Classes
⢠Fit Into Existing Class Hierarchy
⢠Change Naming Conventions
⢠Optimize Internal Implementations
⢠Add Custom Methods
63. Add New Method to Existing Classes
Example: Add a getColumnCount
method to generated managers
1. Create a new
TranslatorDecorator class
2. Write code generation logic
3. Add it to Speedment
64. Agenda
⢠Problem Description
⢠Code Generation in Database Applications
⢠How Does it Work?
⢠Code Example
⢠Controlling the Code Generation
⢠Generate Custom Classes
⢠Add new Method to Existing Classes
⢠Code Example
⢠Additional Features
⢠Beyond Open Source
⢠Questions & Answers
65. Step 1: Creating a New Decorator Class
public class ColumnCountDecorator implements TranslatorDecorator<Table, Interface> {
@Override
public void apply(JavaClassTranslator<Table, Interface> translator) {
translator.onMake(builder -> {
builder.forEveryTable((intrf, table) -> {
// Code generation logic goes here
});
});
}
}
66. Step 2: Write Code Generation Logic
int columnCount = table.columns().count();
intrf.add(Method.of("getColumnCount", int.class)
.default_()
.set(Javadoc.of("Returns the number of columns in this table.")
.add(RETURN.setValue("the column count"))
)
.add("return " + columnCount + ";")
);
67. Step 3: Add it to Speedment
public final class TableEnumPlugin {
@ExecuteBefore(RESOLVED)
protected void install(CodeGenerationComponent codeGen) {
codeGen.put(
Project.class,
TableEnumTranslator.TABLES_ENUM_KEY,
TableEnumTranslator::new
);
codeGen.add(
Table.class,
StandardTranslatorKey.GENERATED_MANAGER,
new ColumnCountDecorator()
);
}
}
Modify an existing translator key
Our new decorator
The same plugin class as we
created earlier
69. Agenda
⢠Problem Description
⢠Code Generation in Database Applications
⢠How Does it Work?
⢠Code Example
⢠Controlling the Code Generation
⢠Generate Custom Classes
⢠Add new Method to Existing Classes
⢠Code Example
⢠Additional Features
⢠Beyond Open Source
⢠Questions & Answers
70. Additional Features
⢠Add GUI Tool components for
custom configuration
⢠Extend the JSON DSL
dynamically with plugins
⢠Automate your build
environment with Maven
⢠Add custom data type
mappers
71. Additional Features
⢠Plugin a custom namer
⢠Generate classes that are related to other domain models
⢠Generate classes for other languages
⢠Style the GUI Tool
73. Agenda
⢠Problem Description
⢠Code Generation in Database Applications
⢠How Does it Work?
⢠Code Example
⢠Controlling the Code Generation
⢠Generate Custom Classes
⢠Add new Method to Existing Classes
⢠Code Example
⢠Additional Features
⢠Beyond Open Source
⢠Questions & Answers
74. Beyond Open-Source
⢠Speedment Enterprise
⢠Run your database queries orders of magnitude faster!
⢠10x, 100x, 1 000x, 10 000x, âŚ
⢠Use the same Stream API
75. How is This Possible?
⢠No changes to user-written code
⢠Alternative stream source
⢠Data is stored in-memory
⢠Generates optimized serializers
for offheap storage