This framework allows to declare the stored procedures calls declarative instead of programmatically. Only the interfaces, the entities and few annotations need to be written. The code for the stored procedures, the necessary entity mappers and the implementations of the DAO interfaces are generated by spBee. It’s also possible to use abstract classes instead of interfaces.
An important point is that there is no magic behind this framework like JPA/Hibernate with crazy reflection, proxies and framework logic. The only logic is how to translate the annotations to the corresponding Java code. Thereby spBee has only about 1700 LoC (measured with sloc).
Everything is done at compile time. Therefore there is no performance shortcoming. No reflection. No proxies.
So some people will wonder why the hell should they use stored procedures? Then read my thoughts about that topic.
Spring is needed because spBee builds on top of org.springframework.jdbc.object.StoredProcedure and uses Spring`s Dependency Injection.
Within Java 1.6 it’s possible to pre process annotations (before the code is compiled). In this pre process phase it’s also possible to generate code (Java source files or Java class files). spBee uses this mechanism to generate the boilerplate code for the stored procedures, the entity mappers and the DAO implementations. spBee generates readable Java code because this allows to set break points in the generated code. Another useful effect is that you can just grab the generated files and work with them - you don’t rely forever on spBee.
Look at spBee#demo to see the a runnable project with minimal configuration to use spBee.
In Maven you need to add spBee as dependency and define spBee`s annotation processor. That’s it.
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <annotationProcessors> <annotationProcessor> at.rseiler.spbee.core.SPBeeAnnotationProcessor </annotationProcessor> </annotationProcessors> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>at.rseiler.spbee</groupId> <artifactId>core</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> </dependencies>
In Spring you need to define the dataSource and the component-scan. The component scan must include the @Dao annotated interfaces and abstract classes.
<context:component-scan base-package="your.package.name"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.hsqldb.jdbc.JDBCDriver"/> <property name="url" value="jdbc:hsqldb:mem:mymemdb"/> <property name="username" value="sa"/> <property name="password" value=""/> </bean>
A simple example of the usage of spBee. The User class and the UserDao interface is all what is needed to call the both stored procedures below and get the User objects.
@Entity public class User { private int id; private String name; private User() { } @MappingConstructor public User(int id, String name) { this(id, name); } public int getId() { return id; } public String getName() { return name; } } @Dao public interface UserDao { // list of entities @StoredProcedure("sp_get_users") List<User> getUsers(); // single entity @StoredProcedure("sp_get_user") public User getUser(int id); } // execution List<User> users = userDao.getUsers(); User user = userDao.getUser(1);
CREATE PROCEDURE sp_get_users() READS SQL DATA DYNAMIC RESULT SETS 1 BEGIN ATOMIC DECLARE resultUser CURSOR WITH RETURN FOR SELECT id, name FROM User; OPEN resultUser; END; CREATE PROCEDURE sp_get_user(idUser INT) READS SQL DATA DYNAMIC RESULT SETS 1 BEGIN ATOMIC DECLARE resultUser CURSOR WITH RETURN FOR SELECT id, name FROM User WHERE id = idUser; OPEN resultUser; END;
The list of all possible return types of a stored procedure method.
It the stored procedure doesn’t return anything. If the stored procedure nevertheless returns something then it’s ignored.
If the stored procedure returns several rows then just use a generic list as return type. If no row is returned by the stored procedure then an empty list will be returned.
The generated code will automatically retrieve the element from the list and return it.
If the stored procedure returns several result sets then we need to model it in Java. Example.
Please note that the order of the Java fields and parameters is important and must match the corresponding stored procedure.
The order of the parameters must be the same as the input parameters of the stored procedure.
As input parameters only basic types are allowed. See StoredProcedureGenerator#getSqlParameter. If you use arrays then they must use the class kind not the primitive kind. E.g. use Integer[] instead of int[]. See StoredProcedureGenerator#getArrayType.
The order of the parameters of the constructor must be the same as the columns of the stored procedure.
As constructor variables only basic types are allowed. See MapperGenerator#getResultSetMethod.
The order of the class fields and the parameters of the constructor must be the same as the returned result sets.
The idea of the interceptor is to log and monitor the stored procedure calls. If a interceptor is configured then before and after the stored procedure call the interceptor is called. It’s not possible to modify the arguments of the stored procedure call. The only thing what would be possible is to throw a RuntimeException to cancel the stored procedure call.
The interceptor class must contain following method signatures:
/** * This method will be called before the stored procedure call. * * @param spName the name of the stored procedure * @param arguments the arguments of the stored procedure * @return the ID object to identify the call in the after method **/ public static Object before(String spName, Object... arguments) /** * This method will be called after the invocation of the stored procedure. * * @param id the ID object (returned from the before method) * @param spName the name of the stored procedure * @param arguments the arguments of the stored procedure */ public static void after(Object id, String spName, Object... arguments)
Then the spbee.properties file is required to be in the class path with the interceptor class configured:
interceptor=at.rseiler.spbee.demo.SpLogger
In the spbee#demo module is an example of a stored procedure logger. See at.rseiler.spbee.demo.SpLogger.
All non spBee related annotations will be added to the methods of the DAO implementation class.
Thereby it’s possible to use the @Transactional annotation on the interface or the abstract class.
Type | Pattern | Description |
---|---|---|
DAO | *.{CLASS_NAME}Impl |
The class name will be the name of the annotated class with Impl as postfix. |
Stored Procedure | *.storedprocedure.{STORED_PROCEDURE_NAME} |
The class name will be the name of the stored procedure (defined in the @StoredProcedure annotation).
But the name will be modified a little bit: The first character will be uppercase, underscores will be removed and the following character will be uppercase. The package is relative to the DAO. |
Mapper | *.mapper.{ENTITY_NAME}{CONSTRUCTOR_NAME}Mapper |
The constructor name is Default if no other name is defined (with @MappingConstructor("some_constructor_name") The package is relative to the entity. |
Name | Target | Description |
---|---|---|
@Dao | TYPE | Annotate the DAO with this annotation. Then spBee will generate the DAO implementation class. |
@Entity | TYPE | Annotate all entities which are used as return types in the stored procedure methods of the DAO. |
@MappingConstructor | CONSTRUCTOR|METHOD|FIELD | Annotate the constructors and give them names. These constructors will be used to create an entity instance. The names allow to use different views/subsets of an entity. Example. |
@ResultSet | TYPE | If an stored procedure returns several result sets then a Java class will represent these result sets. These class must be annotated with @ResultSet. The fields of the class describes the different result sets and the fields can be annotated with: @ReturnNull or @RowMapper. Example. |
@ReturnNull | METHOD|FIELD | If the stored procedure method just returns an entity (not a list of entities) and if no row is returned then the method will return null. Example. |
@RowMapper | METHOD|FIELD | The value defines the class of the mapper which should be used to map the entities. Instead of the generated default mappers. Example. |
@StoredProcedure | METHOD | The value defines the name of the stored procedure. Only methods annotated with @StoredProcedure will be processed by spBee. |
More examples can be found in the spBee#demo module.
@ResultSet public class UserPermissionsResultSet { @RowMapper(SimpleUserMapper.class) private final Optional<User> user; private final List<Permission> permissions; public UserPermissionsResultSet(Optional<User> user, List<Permission> permissions) { this.user = user; this.permissions = permissions; } public Optional<User> getUser() { if (user.isPresent()) { return Optional.of(new Builder(user.get()).permissions(permissions).build()); } return Optional.empty(); } } @Dao public abstract class AbstractUserDao { @StoredProcedure("sp_get_user_with_permissions") public abstract UserPermissionsResultSet getUserWithPermissions(int id); }
public class SimpleUserMapper implements RowMapper<User> { public User mapRow(ResultSet rs, int rowNum) throws SQLException { return new User( rs.getInt(1), "not loaded", new ArrayList<>()); } }
@Dao public interface UserDao { @RowMapper(SimpleUserMapper.class) @StoredProcedure("sp_get_simple_users") List<User> getSimpleUsersWithOwnMapper(); }
@ResultSet public class UserPermissionsResultSet { @RowMapper(SimpleUserMapper.class) private final User user; private final List<Permission> permissions; public UserPermissionsResultSet(User user, List<Permission> permissions) { this.user = user; this.permissions = permissions; } }
@Dao public interface UserDao { @ReturnNull @StoredProcedure("sp_get_simple_user") User getUser(); } @ResultSet public class UserPermissionsResultSet { @ReturnNull private final User user; private final List<Permission> permissions; public UserPermissionsResultSet(Optional<User> user, List<Permission> permissions) { this.user = user; this.permissions = permissions; } }
@Dao public interface UserDao { @StoredProcedure("sp_get_simple_user") Optional<User> getUser(); } @ResultSet public class UserPermissionsResultSet { private final Optional<User> user; private final List<Permission> permissions; public UserPermissionsResultSet(Optional<User> user, List<Permission> permissions) { this.user = user; this.permissions = permissions; } }
@Entity public class User { private final long loginId; private final String userName; private final int age; @MappingConstructor public User(long loginId, String userName, int age) { this.loginId = loginId; this.userName = userName; this.age = age; } @MappingConstructor("userCoreData") public User(long loginId, String userName) { this.loginId = loginId; this.userName = userName; this.age = -1; } } @Dao public abstract class AbstractUserDao { @StoredProcedure("pr_get_user") public abstract User getUser(long loginId); @MappingConstructor("userCoreData") @StoredProcedure("pr_get_user_core_data") public abstract User getUserCoreData(long loginId); }