Upload
speedment-inc
View
83
Download
0
Embed Size (px)
Citation preview
Tutorial: How to Generate Customized Java 8 Code from Your DatabasePer MinborgCTO, Speedment, Inc
Emil ForslundDeveloper, Speedment, Inc.
Every Decision a Developer Makes is a Trade-off
The best code is no code at all
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?
About UsPer 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 • 1.5 years of mascot experience
Agenda• Problem Description• Code Generation in Database Applications
• How Does it Work?• Hands on Demo
• Controlling the Code Generation• Generate Custom Classes• Add new Method to Existing Classes• Hands on Demo
• Additional Features• Real World Examples• Questions & Answers
Agenda• Problem Description• Code Generation in Database Applications
• How Does it Work?• Hands on Demo
• Controlling the Code Generation• Generate Custom Classes• Add new Method to Existing Classes• Hands on Demo
• Additional Features• Real World Examples• Questions & Answers
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... }
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
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
Speedment Workflow
customers.stream() .filter(…) .filter(…) .map(…) .collect(toList());
Step 1: Generate Code
Step 2: Write Logic Step 3: Run Application
Step 4: Iterate
Agenda• Problem Description• Code Generation in Database Applications
• How Does it Work?• Hands on Demo
• Controlling the Code Generation• Generate Custom Classes• Add new Method to Existing Classes• Hands on Demo
• Additional Features• Real World Examples• Questions & Answers
Tool
Artifacts• com.speedment:
• runtime• generator• tool• speedment-maven-plugin
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
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
Querying the Database using Streams• Queries are expressed using
Java 8 streams• Streams are analyzed to
produce high-performance queries
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’;
Querying the Database using Streams
SELECT * FROM 'customer'
REGION.equal(NORTH_AMERICA)
REGISTERED.greaterOrEqual(2016-01-01)
count()
Source
Filter
Filter
Term.
Pipeline
Querying the Database using Streams
SELECT * FROM 'customer'WHERE 'customer'.'region' = ‘North America’
REGION.equal(NORTH_AMERICA)
REGISTERED.greaterOrEqual(2016-01-01)
count()
Source
Filter
Filter
Term.
Pipeline
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()
Source
Filter
Term.
Pipeline
Querying the Database using Streams
SELECT COUNT('id') FROM 'customer'WHERE 'customer'.'region' = ‘North America’
AND 'customer'.'registered' >= ‘2016-01-01’;
count()
Source
Term.
Pipeline
Querying the Database using Streams
SELECT COUNT('id') FROM 'customer'WHERE 'customer'.'region' = ‘North America’
AND 'customer'.'registered' >= ‘2016-01-01’;Source
Pipeline
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… .map(JsonEncoder.allOf(customers)::apply) .collect(joining(“, ”))+”]”;
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());
Application Example• Total Count of Customers
• Located in North America• Registered This Year
Step 1: Getting Speedment using Maven
<plugin> <groupId>com.speedment</groupId> <artifactId>speedment-maven-plugin</artifactId> <version>3.0.0-EA</version>
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency> </dependencies></plugin>
Generate source files based on database
The JDBC connector to use
Step 2: Initializing Speedment
SalesApplication app = new SalesApplicationBuilder() .withPassword("qwerty") .build();
CustomerManager customers = app.getOrThrow(CustomerManager.class);
These classes are generated automaticallyInstance is configured using Builder-pattern
A manager class is generated for every database table
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();
Step 4: Output System.out.println( "A total of %d customers from %s " + "have registered this year.", count, fromWhere.name() );
Full Application public static void main(String… args) { SalesApplication app = new SalesApplicationBuilder() .withPassword("qwerty") .build();
CustomerManager customers = app.getOrThrow(CustomerManager.class);
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();
System.out.println( "A total of %d customers from %s " + "have registered this year.", count, fromWhere.name() ); }
OutputPers-MacBook-Pro:~ pemi$ java –jar salesapplication.jar
A total of 354 customers from NORTH_AMERICA have registered this year.
Live Demo: Speedment• Generate Domain Model• Write Java Stream that:
• Determine the ID of a certain city• Alphabetical list of last names of
salespersons in that city
• Execute
Agenda• Problem Description• Code Generation in Database Applications
• How Does it Work?• Hands on Demo
• Controlling the Code Generation• Generate Custom Classes• Add new Method to Existing Classes• Hands on Demo
• Additional Features• Real World Examples• Questions & Answers
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?
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
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
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
Modular Design
Database Domain Model• Project• Dbms• Schema• Table
• Column• PrimaryKey• ForeignKey• Index
List<Table> tables = project.dbmses() .flatMap(Dbms::schemas) .flatMap(Schema::tables) .collect(toList());
JSON Configuration (DSL){ "config" : { "name" : "sales",
"dbmses" : [{ "name" : "db0", "typeName" : "MySQL", "ipAddress" : "127.0.0.1", "username" : "root",
"schemas" : [{ "name" : "sales", "tables" : [ { "name" : "city" }, { "name" : "salesperson" } ] }] }] }}
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
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
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
Type Mappers• Controls how columns are implemented• Runtime conversion between Database and Java types
java.sql.Timestamp long
Generation vs. Templates• Separation of concerns• Easily change code style• Minimize maintenance• Maximize reusability
Generate a New Custom Class
1. Create a new Translator2. Model how the new class should
look3. Define a Plugin Class4. Include it in project pom.xml
Example: Generate a Point class
Step 1: Create a New Translator Classpublic 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 invokedforEvery(Project|Dbms|Schema|Table|Column|…)
Step 2: The makeCodeGenModel - methodclazz.public_() .add(Field.of(“x”, int.class) .private_().final_() ) .add(Field.of(“y”, int.class) .private_().final_() ) .add(Constructor.of().public_() .add(Field.of(“x”, int.class)) .add(Field.of(“y”, int.class)) .add(“this.x = x;”, “this.y = y;” ) ) .add(Method.of(“getX”, int.class).public_() .add(“return x;”) ) .add(Method.of(“getY”, int.class).public_() .add(“return y;”) ) .call(new AutoEquals<>()) .call(new AutoToString<>());
Step 3: Define a Plugin Class
public class PointPlugin {
@ExecuteBefore(RESOLVED) protected 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
Step 4: Include it in Project pom.xml<plugin> <groupId>com.speedment</groupId> <artifactId>speedment-maven-plugin</artifactId> <version>3.0.0-EA</version>
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency>
<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
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(“}”); }}
A More Concrete Example
1. Create a new Translator2. Model how the new class
should look3. Define a Plugin Class4. Include it in project pom.xml
Example: Generate an Enum of tables in the database
Step 1: Create a New Translator Classpublic 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 invokedforEvery(Project|Dbms|Schema|Table|Column|…)
Step 2: The makeCodeGenModel - methodDocumentDbUtil.traverseOver(project, Table.class) .map(Table::getJavaName) .map(getSupport().namer()::javaStaticFieldName) .sorted() .map(EnumConstant::of) .forEachOrdered(clazz::add);
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 ); }}
Step 4: Include it in Project pom.xml<plugin> <groupId>com.speedment</groupId> <artifactId>speedment-maven-plugin</artifactId> <version>3.0.0-EA</version>
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency>
<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
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:
Generate all the Things• Gson Adapters• Spring Configuration Files• REST Controllers• and much more…
Agenda• Problem Description• Code Generation in Database Applications
• How Does it Work?• Hands on Demo
• Controlling the Code Generation• Generate Custom Classes• Add new Method to Existing Classes• Hands on Demo
• Additional Features• Real World Examples• Questions & Answers
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?
Add New Method to Existing Classes• Fit Into Existing Class Hierarchy• Change Naming Conventions• Optimize Internal Implementations• Add Custom Methods
Add New Method to Existing ClassesExample: Add a getColumnCount method to generated managers
1. Create a new TranslatorDecorator class
2. Write code generation logic3. Add it to Speedment
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 }); }); }}
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 + ";"));
Step 3: Add it to Speedmentpublic 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
ExecuteWhen the project is regenerated, a new method is added to each manager
Agenda• Problem Description• Code Generation in Database Applications
• How Does it Work?• Hands on Demo
• Controlling the Code Generation• Generate Custom Classes• Add new Method to Existing Classes• Hands on Demo
• Additional Features• Real World Examples• Questions & Answers
Demo: Advanced Plugin• Use the Speedment Spring
Plugin to generate a working REST API
Agenda• Problem Description• Code Generation in Database Applications
• How Does it Work?• Hands on Demo
• Controlling the Code Generation• Generate Custom Classes• Add new Method to Existing Classes• Hands on Demo
• Additional Features• Real World Examples• Questions & Answers
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
Additional Features• Plugin a custom namer• Generate classes that are related to other domain models• Generate classes for other languages• Style the GUI Tool
Database Connectors
Open Source• MySQL• PostgreSQL• MariaDB
Enterprise• Oracle• Microsoft SQL server• dB2
Agenda• Problem Description• Code Generation in Database Applications
• How Does it Work?• Hands on Demo
• Controlling the Code Generation• Generate Custom Classes• Add new Method to Existing Classes• Hands on Demo
• Additional Features• Real World Examples• Questions & Answers
In-JVM-Memory Data Store• Alternative stream source• Uses code generation • Optimized serializers for offheap
storage• No changes to user-written code• 10-100x faster queries
Ext Speeder
In-JVM-Memory !
Q&Awww.speedment.org
github.com/speedment
@speedment
www.speedment.com
Linkedin: Per MinborgEmil Forslund