I am setting up a Java Spring project with multiple StoredProcedures to two completely different Oracle databases.
It is not allowed to use any auto-generated SQL.
I didn't find anywhere a complete solution or implementation example so I will try to sum up the question and clean solution here.
I sincerely hope this will help someone someday.
You will need a working Spring Boot project.
Please let me know if there is anything confusing and I should explain it better.
Database connection settings
The Database connection Properties (db.properties)
Please place this file in resources folder.
db1.datasource.url=jdbc:oracle:thin:#url:sid
db1.datasource.username=username
db1.datasource.password=password
db1.datasource.driver-class-name=oracle.jdbc.OracleDriver
db2.datasource.url=jdbc:oracle:thin:#url:sid
db2.datasource.username=username
db2.datasource.password=password
db2.datasource.driver-class-name=oracle.jdbc.OracleDriver
Database configuration class (DbConfiguration.java)
#Configuration
#Order(1)
#PropertySource("classpath:/db.properties")
public class DbConfiguration {
/**
* Configuration beans for establishing a connection to db1 database.
* The primary database dataSource is automatically populated to the constructor in StoredProcedure extended class.
*/
#Bean
#Primary
#ConfigurationProperties("db1.datasource")
public DataSourceProperties db1DataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "db1")
#Primary
public DataSource db1DataSource() {
return db1DataSourceProperties().initializeDataSourceBuilder().build();
}
/**
* Configuration beans for establishing a connection to db2 database.
*/
#Bean
#ConfigurationProperties("db2.datasource")
public DataSourceProperties db2DataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "db2")
public DataSource db2DataSource() {
return db2DataSourceProperties().initializeDataSourceBuilder().build();
}
Db1 stored procedure classes
StoredProcedure for retrieving a single payment (SPGetSinglePayment.java)
/**
* The type Sp get payment.
* A StoredProcedure class where we define IN and OUT parameters.
*/
#Component
public class SPGetSinglePayment extends StoredProcedure {
public static final String PROCEDURE_GET_PAYMENT = "GET_PAYMENT";
public static final String PROCEDURE_GET_PAYMENT_PARAM_IN_ID = "P_PAYMENT_ID";
public static final String PROCEDURE_GET_PAYMENT_PARAM_OUT_RESULT = "PAYMENT_RESULT";
public SPGetSinglePayment(final DataSource dataSource) {
super(dataSource, PACKAGE_NAME + PROCEDURE_GET_PAYMENT);
declareParameter(new SqlParameter(PROCEDURE_GET_PAYMENT_PARAM_IN_ID, OracleTypes.VARCHAR));
declareParameter(new SqlOutParameter(PROCEDURE_GET_PAYMENT_PARAM_OUT_RESULT, OracleTypes.CURSOR));
compile();
}
}
StoredProcedure Response builder class (SinglePaymentResponseBuilder.java)
/**
* The type Payment response builder. Gets an object from Oracle DB and populates POJOs.
*/
#Component
public class SinglePaymentResponseBuilder {
/**
* Builds list of payment transaction details from stored procedure result set.
*
* #param getPaymentObject the object containing payment details result set
* #param getItineraryDataObject the object containing itinerary data result set
* #return list of payment details for payment
*/
public List<SinglePaymentDto> build(final Object getPaymentObject, final Object getItineraryDataObject) {
final List<Map<String, Object>> spMap = getListOfObjectMaps(getPaymentObject);
final List<SinglePaymentDto> response = new ArrayList<>();
for (Map<String, Object> dtos : spMap) {
SinglePaymentDto payment = new SinglePaymentDto(
new PaymentInfo(getStringValue(dtos.get(PaymentInfo.PAYMENT_ID)),
... build and return response
StoredProcedure Helper class (StoredProcedureHelper.java)
Here we actually execute two stored procedures to a single database.
/**
* Contains methods to call Oracle prepared statements. Responsible for handling procedure specific input and output parameters.
*/
#Component
public class StoredProcedureHelper {
public static final String PACKAGE_NAME = "A_PACKAGE_NAME.";
private final SPGetSinglePayment getSinglePayment;
private final SinglePaymentResponseBuilder singlePaymentResponseBuilder;
#Autowired
public StoredProcedureHelper(
final SPGetSinglePayment getSinglePayment,
final SinglePaymentResponseBuilder singlePaymentResponseBuilder,
...){
this.getSinglePayment = getSinglePayment;
this.singlePaymentResponseBuilder = singlePaymentResponseBuilder;
...
}
/**
* Calls stored procedure to get all payment details for given payment.
*
* #param id the payment id
* #return payment details
*/
public List<SinglePaymentDto> findSinglePayment(final String id) {
LOG.info(LOG_PATTERN, SPGetSinglePayment.class.getSimpleName(),
PACKAGE_NAME, PROCEDURE_GET_PAYMENT);
Object spGetPaymentResult = getSinglePayment.execute(id).get(PROCEDURE_GET_PAYMENT_PARAM_OUT_RESULT);
Object spGetItineraryDataResult = getItineraryData.execute(id).get(PROCEDURE_GET_ITINERARY_DATA_PARAM_OUT_RESULT);
return singlePaymentResponseBuilder.build(spGetPaymentResult, spGetItineraryDataResult);
}
Db2 stored procedure classes
StoredProcedure for retrieving a decrypted toothbrush from its identifier token (SPGetToothbrush.java)
I'd like to expose just below class here. Please note that if you'd like to use a db2 you will have to define it by #Qualifier annotation.
Other classes will follow the above pattern for each stored procedure.
On request I can also provide unit test examples.
/**
* The type Sp get toothbrush.
* A StoredProcedure class where we define IN and OUT parameters.
*/
#Component
public class SPGetToothbrush extends StoredProcedure {
public static final String PROCEDURE_GET_TOOTHBRUSH = "GET_IDENTIFIER";
public static final String PROCEDURE_GET_TOOTHBRUSH_PARAM_IN_INSTRUMENT_ID = "P_TOKEN";
public static final String PROCEDURE_GET_TOOTHBRUSH_PARAM_OUT_RESULT = "OUT_IDENTIFIER";
/**
* Instantiates a new Sp get toothbrush.
*
* #param dataSource is populated by db2 properties by use of #Qualifier.
*/
public SPGetToothbrush(#Qualifier("db2") final DataSource dataSource) {
super(dataSource, StoredProcedureToothbrushHelper.PACKAGE_NAME + PROCEDURE_GET_TOOTHBRUSH);
declareParameter(new SqlParameter(PROCEDURE_GET_TOOTHBRUSH_PARAM_IN_INSTRUMENT_ID, OracleTypes.VARCHAR));
declareParameter(new SqlOutParameter(PROCEDURE_GET_TOOTHBRUSH_PARAM_OUT_RESULT, OracleTypes.VARCHAR));
compile();
}
}
I'm trying to use QueryDSL with Spring Data JPA, I want to use findAll with pagination but the count is always performed, also if return type is a List.
I don't need this count because it is really slow and I could loose the benefit of pagination.
Any solutions for this problem?
This is the count(), it requires about 30 seconds on MySQL:
Mysql too slow on simple query between two tables
In any case I don't want repeat the count for each page I require, this information is required just for first call.
Since QuerydslPredicateExecutor does not support returning Slice as the return value of findAll(Predicate, Pageable), so the Count Query seems unavoidable. But you can define a new base repository interface and implement the findAll method in a way that it does not issue a count query for pagination. For starters, you should define an interface which will be used as the base interface for all other Repositories:
/**
* Interface for adding one method to all repositories.
*
* <p>The main motivation of this interface is to provide a way
* to paginate list of items without issuing a count query
* beforehand. Basically we're going to get one element more
* than requested and form a {#link Page} object out of it.</p>
*/
#NoRepositoryBean
public interface SliceableRepository<T, ID extends Serializable>
extends JpaRepository<T, ID>,
QuerydslPredicateExecutor<T> {
Page<T> findAll(Predicate predicate, Pageable pageable);
}
Then, implement this interface like:
public class SliceableRepositoryImpl<T, ID extends Serializable>
extends QueryDslJpaRepository<T, ID>
implements SliceableRepository<T, ID> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public SliceableRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
path = DEFAULT_ENTITY_PATH_RESOLVER.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
int oneMore = pageable.getPageSize() + 1;
JPQLQuery query = createQuery(predicate)
.offset(pageable.getOffset())
.limit(oneMore);
Sort sort = pageable.getSort();
query = querydsl.applySorting(sort, query);
List<T> entities = query.list(path);
int size = entities.size();
if (size > pageable.getPageSize())
entities.remove(size - 1);
return new PageImpl<>(entities, pageable, pageable.getOffset() + size);
}
}
Basically, this implementation would fetch one more element than requested size and use the result for constructing a Page. Then you should tell Spring Data to use this implementation as the repository base class:
#EnableJpaRepositories(repositoryBaseClass = SliceableRepositoryImpl.class)
And finally extend the SliceableRepository as your base interface:
public SomeRepository extends SliceableRepository<Some, SomeID> {}
FYI there is a spring jira issue:
https://jira.spring.io/browse/DATAJPA-289
Let's vote for this improvement
In case anyone lands here looking for how to achieve the same affect in Spring Data MongoDB as Ali did above for Spring Data JPA, here's my solution modeled on his:
import java.io.Serializable;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.QueryDslMongoRepository;
import org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QSort;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.core.EntityInformation;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.mongodb.AbstractMongodbQuery;
/**
* Custom extension of {#link QueryDslMongoRepository} that avoids unnecessary MongoDB "count"
* operations
* <p>
* {#link QueryDslPredicateExecutor#findAll(Predicate, Pageable)} returns a {#link Page} at
* potentially great expense because determining the {#link Page}'s "totalElements" property
* requires doing a potentially expensive MongoDB "count" operation. We'd prefer a "findAll"-like
* method that returns a {#link Slice} (which doesn't have a "totalElements" property) but no such
* method exists. See {#link #findAll(Predicate, Pageable)} for more details.
*
* #see https://github.com/spring-projects/spring-data-commons/issues/1011
* #see https://stackoverflow.com/questions/37254385/querydsl-springdata-jpa-findall-how-to-avoid-count
*/
public class MyQueryDslMongoRepository<T, ID extends Serializable> extends QueryDslMongoRepository<T, ID>
implements MyAbstractRepository<T, ID> {
private final PathBuilder<T> builder;
private final EntityInformation<T, ID> entityInformation;
private final MongoOperations mongoOperations;
public BTQueryDslMongoRepository(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations) {
this(entityInformation, mongoOperations, SimpleEntityPathResolver.INSTANCE);
}
public BTQueryDslMongoRepository(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations,
EntityPathResolver resolver) {
super(entityInformation, mongoOperations, resolver);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.entityInformation = entityInformation;
this.mongoOperations = mongoOperations;
}
/**
* An override of our superclass method to return a fake but cheaper-to-compute {#link Page}
* that's adequate for our purposes.
*/
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable) {
int pageSize = pageable.getPageSize();
SpringDataMongodbQuery<T> query = new SpringDataMongodbQuery<T>(mongoOperations, entityInformation.getJavaType())
.where(predicate)
.offset(pageable.getOffset())
.limit(pageSize + 1);
applySorting(query, pageable.getSort());
List<T> entities = query.fetch();
int numFetched = entities.size();
if (numFetched > pageSize) {
entities.remove(numFetched - 1);
}
return new PageImpl<T>(entities, pageable, pageable.getOffset() + numFetched);
}
/**
* Applies the given {#link Sort} to the given {#link MongodbQuery}.
* <p>
* Copied from {#link QueryDslMongoRepository}
*/
private AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> applySorting(
AbstractMongodbQuery<T, SpringDataMongodbQuery<T>> query, Sort sort) {
if (sort == null) {
return query;
}
// TODO: find better solution than instanceof check
if (sort instanceof QSort) {
List<OrderSpecifier<?>> orderSpecifiers = ((QSort) sort).getOrderSpecifiers();
query.orderBy(orderSpecifiers.toArray(new OrderSpecifier<?>[orderSpecifiers.size()]));
return query;
}
for (Order order : sort) {
query.orderBy(toOrder(order));
}
return query;
}
/**
* Transforms a plain {#link Order} into a QueryDsl specific {#link OrderSpecifier}.
* <p>
* Copied from {#link QueryDslMongoRepository}
*/
#SuppressWarnings({ "rawtypes", "unchecked" })
private OrderSpecifier<?> toOrder(Order order) {
Expression<Object> property = builder.get(order.getProperty());
return new OrderSpecifier(
order.isAscending() ? com.querydsl.core.types.Order.ASC : com.querydsl.core.types.Order.DESC, property);
}
}
#NoRepositoryBean
public interface MyAbstractRepository<T, ID extends Serializable> extends Repository<T, ID>,
QueryDslPredicateExecutor<T> {
#Override
Page<T> findAll(Predicate predicate, Pageable pageable);
}
The above works for Spring Data MongoDB 1.10.23 but I assume can be modified to be made to work for more modern versions.
Based on the answer of Ali Dehghani we build the following for querydsl 4.2.1, because the querydsl syntax changed in the current version 4.x
Repository Interface:
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Predicate;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
public interface SliceableRepository<T> {
Slice<T> findAllSliced(EntityPath<T> entityPath, Predicate predicate, Pageable pageable);
}
Repository Implementation:
(Must be named "<Interface-Name>Impl")
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import javax.persistence.EntityManager;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.data.jpa.repository.support.Querydsl;
public class SliceableRepositoryImpl<T> implements SliceableRepository<T> {
private final EntityManager entityManager;
private final JPAQueryFactory jpaQueryFactory;
public SliceableRepositoryImpl(EntityManager entityManager) {
this.entityManager = entityManager;
this.jpaQueryFactory = new JPAQueryFactory(entityManager);
}
#Override
public Slice<T> findAllSliced(final EntityPath<T> entityPath, final Predicate predicate,
final Pageable pageable) {
final Querydsl querydsl = new Querydsl(entityManager,
new PathBuilder<>(entityPath.getType(), entityPath.getMetadata()));
final int oneMore = pageable.getPageSize() + 1;
final JPAQuery<T> query = this.jpaQueryFactory.selectFrom(entityPath)
.where(predicate)
.offset(pageable.getOffset())
.limit(oneMore);
final JPQLQuery<T> querySorted = querydsl.applySorting(pageable.getSort(), query);
final List<T> entities = querySorted.fetch();
final int size = entities.size();
// If there was one more result than requested from the pageable,
// then the slice gets "hasNext"=true
final boolean hasNext = size > pageable.getPageSize();
if (hasNext) {
entities.remove(size - 1);
}
return new SliceImpl<>(entities, pageable, hasNext);
}
}
Use the new repository as fragment in your other repositories:
public SomeRepository extends JpaRepository<Some, Long>, SliceableRepository<Some> {
}
#EnableJpaRepositories(repositoryBaseClass = SliceableRepositoryImpl.class) is NOT needed
Then use it like:
public class MyService {
#Autowired
private final SomeRepository someRepository;
public void doSomething() {
Predicate predicate = ...
Pageable pageable = ...
// QSome is the generated model class from querydsl
Slice<Some> result = someRepository.findAllSliced(QSome.some, predicate, pageable);
}
}
I am using couchbase3 with spring-data-couchbase, and want to query data using spring data repository with multiple columns.
public interface UserAccountRepository extends CrudRepository<UserAccount, Long> {
public UserAccount findByEmail(Query eMail);
public UserAccount findByEmailAndStatus(Query query); // fn with multiple column, but not getting the result
}
How should I write Map function and Reduce function for the same?
For the function findByEmail(Query eMail); to work, I have added the view with Map fn()
function (doc, meta) {
emit(doc.email,doc);
}
This view have email as key, and value is the document.
But if i need to query using email and status? How should the view look like ?
I have seen this link, but not very clear.
https://stackoverflow.com/questions/28938755
I was able make springdata function to invoke a compound Key view.
My Document name is : Data
Compound Key View
function (doc, meta) {
if(doc && doc._class == "com.couchbase.entity.Data"){
emit([doc.key1, doc.key2], doc);
}
}
SpringData Repository Inferface shown below :
package com.couchbase.repository;
import java.util.List;
import org.springframework.data.couchbase.core.view.View;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import com.couchbase.client.protocol.views.Query;
import com.couchbase.entity.Data;
public interface DataRepository extends CrudRepository<Data, String> {
#View(designDocument="Data",viewName="findByKey1AndKey2")
public List<Data> findByKey1AndKey2(Query query);
}
Test Class shown below :
import com.couchbase.client.protocol.views.ComplexKey;
import com.couchbase.client.protocol.views.Query;
public class DataTest extends WebAppConfigurationAware{
#Autowired
private DataRepository dataRepository;
#Test
public void testStringDataCompoundQuery(){
Object[] objArr = new Object[2];
objArr[0] = "aaa";
objArr[1] = 1;
Query query = new Query();
query.setKey(ComplexKey.of(objArr));
System.out.println(dataRepository.findByKey1AndKey2(query));
}
}
If this was useful for you, please up vote
You could use compound key like describe in the documentation here: http://docs.couchbase.com/developer/dev-guide-3.0/compound-keys.html
I was wondering if I can somehow autowire jdbcTemplate in the ObjectMother for a junit 4 integration Test.
The purpose of the test is to test that if you attach a document to an employee, you cannot attach afterwards a document with the same name.
For that I made an EmployeeMother that has a method that creates an Employee and a method that inserts it, using jdbcTemplate. If I try to autowire the jdbcTemplate in EmployeeMother it is null(test returns NPE on the first update). It seems I only have access to the applicationContexts from the test itself.
Currently I set it from the test itself, but I would like not to, because I will create more ObjectMothers for different objects and would like not to set the jdbcTemplate for all of them.
Here are the two classes (I deleted the company name from the package and imports):
EmployeeMother:
package com.domain.objMother;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import com.domain.vo.Company;
import com.domain.vo.Contact;
import com.domain.vo.Employee;
import com.domain.vo.Identification;
import com.domain.vo.Role;
public class EmployeeMother {
private final Log log = LogFactory.getLog(getClass());
protected JdbcTemplate jdbcTemplate;
private Employee empJohnDoe;
/**
*
* #return returns an admin user with username djohn
*/
public Employee getEmpJohnDoe() {
empJohnDoe = new Employee();
empJohnDoe.setUserName("djohn");
Role role = new Role();
//as only the id of the role is not nullable I set it as 1 = admin
role.setId(new Long(1));
empJohnDoe.setRole(role);
empJohnDoe.setCompany(new Company());
Identification identity = new Identification();
identity.setFirstName("John");
identity.setLastName("Doe");
identity.setContact(new Contact());
empJohnDoe.setIdentity(identity);
return empJohnDoe;
}
public void setEmpJohnDoe(Employee empJohnDoe) {
this.empJohnDoe = empJohnDoe;
}
/**
* Important! this insert does not cover some details of the Employee:
* It inserts null in the following columns:
* pswd,
* image,
* cnt_id, - should be a list of associated contacts
* salt,
* is_active,
* default_work_hours
* The insert in TAB_IDENTIFICATIONS triggers TRIG_IDNT that inserts stuff in an audit table
* For it to work we need to register a logged user
* That's why we call PAC_SECURITY.PRO_SETCTX('emp_user_name','adminUserName'); (i used an admin)
* I preferred doing this rather than inserting djohn in TAB_EMPLOYEES,
* registering djohn as logged then inserting an identity in TAB_IDENTIFICATIONS
* and then updating djohn with the new identity
* #param emp - Employee to be inserted
*/
public void insert(Employee emp){
jdbcTemplate.update("call PAC_SECURITY.PRO_SETCTX('emp_user_name','adminUserName')");
Long identityId = jdbcTemplate.queryForObject("select max(ti.ID)+1 from tab_identifications ti", Long.class);
emp.getIdentity().setId(identityId);
jdbcTemplate.update(""+
" insert into tab_identifications ("+
" id, first_name, middle_name, last_name, cnp, ci_char, ci_number, birth_date, invalidity,"+
" cas_name, ci_issue_date, ci_issuer, cnt_id"+
" )" +
" values (?,?,?,?,?,?,?,?,?,?,?,?,?)",
new Object[]{emp.getIdentity().getId(), emp.getIdentity().getFirstName(), emp.getIdentity().getMiddleName(),
emp.getIdentity().getLastName(), emp.getIdentity().getCnp(), emp.getIdentity().getIdCardSerial(),
emp.getIdentity().getIdCardNumber(), emp.getIdentity().getBirthDate(),
emp.getIdentity().getInvalidity(), emp.getIdentity().getCAS(), emp.getIdentity().getCiIssueDate(),
emp.getIdentity().getCiIssuer(), emp.getIdentity().getContact().getId()}
);
Long id = jdbcTemplate.queryForObject("select max(te.ID)+1 from tab_employees te", Long.class);
emp.setId(id);
jdbcTemplate.update(""+
" insert into tab_employees (id, user_name, code, pswd, idnt_id, role_id, comp_id, image, "+
" hire_date, cnt_id, salt, is_expired, is_active, default_work_hours "+
" )" +
" values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
new Object[]{emp.getId(), emp.getUserName(), emp.getCode(), null, emp.getIdentity().getId(),
emp.getRole().getId(), emp.getCompany().getId(), null, emp.getHireDate(),
null, null, emp.getIsExpired(), null, null
}
);
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
HomeEmployeeServiceImplIntegrationTest:
package com.employee.service.impl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.domain.vo.Document;
import com.domain.vo.Employee;
import com.domain.objMother.EmployeeMother;
import com.employee.service.HomeEmployeeService;
import com.util.annotations.TransactionalDevTest;
import static org.junit.Assert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#TransactionalDevTest
public class HomeEmployeeServiceImplIntegrationTest {
#Autowired
protected JdbcTemplate jdbcTemplate;
#Autowired
HomeEmployeeService homeEmployeeService;
EmployeeMother empMother = new EmployeeMother();
Employee empJohnDoe;
#Before
public void beforeEachTest() throws Exception {
empMother.setJdbcTemplate(jdbcTemplate);
empJohnDoe = empMother.getEmpJohnDoe();
empMother.insert(empJohnDoe);
}
/**
* You should not be able to add a document with the same name
* <code>uploadDocument</code> should not insert the document if it has the same name
*/
#Test
public void shouldNotBeAbleToAddSameDoc(){
Document doc = new Document();
Long id = jdbcTemplate.queryForObject("select max(td.ID)+1 from tab_documents td", Long.class);
doc.setId(id);
doc.setFileName("SameOldDocument");
homeEmployeeService.uploadDocument(empJohnDoe.getIdentity(), doc);
id = jdbcTemplate.queryForObject("select max(td.ID)+1 from tab_documents td", Long.class);
doc.setId(id);
homeEmployeeService.uploadDocument(empJohnDoe.getIdentity(), doc);
Long docNo = jdbcTemplate.queryForObject("select count(id) from tab_documents td where doc_file_name = '" + doc.getFileName() + "'", Long.class);
if(docNo.compareTo(new Long(2)) == 0){
assertEquals("I was able to add a document twice with the same name!", new Long(1), docNo);
}
else{
assertEquals("Something went wrong when adding two documents with the same name! The document should be added once or twice, but the result is different!", new Long(1), docNo);
}
}
TransactionalDevTest is where I define all the applicationContexts used.
The code above works, but I would like to separate EmployeeMother's code and add IdentificationMother and probably DocumentMother each with it's object and insert. I would also like not to set jdbcTemplate for each ObjectMother (things can become ambiguous, some setting jdbcTemplate from the test, some setting it from another ObjectMother).
Thanks in advance.
I was helped by a collegue and this is what I did:
I made an annotation for ObjectMothers:
package com.util.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ObjectMother {}
I added this annotation to my EmployeeMother:
#ObjectMother
public class EmployeeMother {
and autowired my jdbcTemplate in EmployeeMother (also deleted the getter and setter)
#Autowired
protected JdbcTemplate jdbcTemplate;
I added to an applicationContext:
The xmlns:
xmlns:context="http://www.springframework.org/schema/context"
with schema location
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
and
<context:component-scan base-package="com.domain.objMother">
<context:include-filter type="annotation" expression="com.util.annotations.ObjectMother"/>
</context:component-scan>
This searches all classes annotated with ObjectMother and adds them to the applicationContext (so that you don't have to add them all one by one).
And finnaly I used EmployeeMother autowired in HomeEmployeeServiceImplIntegrationTest and deleted any reference to jdbcTemplate from EmployeeMother in the same class:
#Autowired
EmployeeMother empMother;
Final classes:
EmployeeMother:
package com.domain.objMother;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import com.domain.vo.Company;
import com.domain.vo.Contact;
import com.domain.vo.Employee;
import com.domain.vo.Identification;
import com.domain.vo.Role;
import com.util.annotations.ObjectMother;
#ObjectMother
public class EmployeeMother {
private final Log log = LogFactory.getLog(getClass());
#Autowired
protected JdbcTemplate jdbcTemplate;
private Employee empJohnDoe;
/**
*
* #return returns an admin user with username djohn
*/
public Employee getEmpJohnDoe() {
empJohnDoe = new Employee();
empJohnDoe.setUserName("djohn");
Role role = new Role();
//as only the id of the role is not nullable I set it as 1 = admin
role.setId(new Long(1));
empJohnDoe.setRole(role);
empJohnDoe.setCompany(new Company());
Identification identity = new Identification();
identity.setFirstName("John");
identity.setLastName("Doe");
identity.setContact(new Contact());
empJohnDoe.setIdentity(identity);
return empJohnDoe;
}
public void setEmpJohnDoe(Employee empJohnDoe) {
this.empJohnDoe = empJohnDoe;
}
/**
* Important! this insert does not cover some details of the Employee:
* It inserts null in the following columns:
* pswd,
* image,
* cnt_id, - should be a list of associated contacts
* salt,
* is_active,
* default_work_hours
* The insert in TAB_IDENTIFICATIONS triggers TRIG_IDNT that inserts stuff in an audit table
* For it to work we need to register a logged user
* That's why we call PAC_SECURITY.PRO_SETCTX('emp_user_name','adminUserName'); (i used an admin)
* I preferred doing this rather than inserting djohn in TAB_EMPLOYEES,
* registering djohn as logged then inserting an identity in TAB_IDENTIFICATIONS
* and then updating djohn with the new identity
* #param emp - Employee to be inserted
*/
public void insert(Employee emp){
jdbcTemplate.update("call PAC_SECURITY.PRO_SETCTX('emp_user_name','adminUserName')");
Long identityId = jdbcTemplate.queryForObject("select max(ti.ID)+1 from tab_identifications ti", Long.class);
emp.getIdentity().setId(identityId);
jdbcTemplate.update(""+
" insert into tab_identifications ("+
" id, first_name, middle_name, last_name, cnp, ci_char, ci_number, birth_date, invalidity,"+
" cas_name, ci_issue_date, ci_issuer, cnt_id"+
" )" +
" values (?,?,?,?,?,?,?,?,?,?,?,?,?)",
new Object[]{emp.getIdentity().getId(), emp.getIdentity().getFirstName(), emp.getIdentity().getMiddleName(),
emp.getIdentity().getLastName(), emp.getIdentity().getCnp(), emp.getIdentity().getIdCardSerial(),
emp.getIdentity().getIdCardNumber(), emp.getIdentity().getBirthDate(),
emp.getIdentity().getInvalidity(), emp.getIdentity().getCAS(), emp.getIdentity().getCiIssueDate(),
emp.getIdentity().getCiIssuer(), emp.getIdentity().getContact().getId()}
);
Long id = jdbcTemplate.queryForObject("select max(te.ID)+1 from tab_employees te", Long.class);
emp.setId(id);
jdbcTemplate.update(""+
" insert into tab_employees (id, user_name, code, pswd, idnt_id, role_id, comp_id, image, "+
" hire_date, cnt_id, salt, is_expired, is_active, default_work_hours "+
" )" +
" values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
new Object[]{emp.getId(), emp.getUserName(), emp.getCode(), null, emp.getIdentity().getId(),
emp.getRole().getId(), emp.getCompany().getId(), null, emp.getHireDate(),
null, null, emp.getIsExpired(), null, null
}
);
}
}
HomeEmployeeServiceImplIntegrationTest:
package com.employee.service.impl;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.domain.vo.Document;
import com.domain.vo.Employee;
import com.domain.objMother.EmployeeMother;
import com.employee.service.HomeEmployeeService;
import com.util.annotations.TransactionalDevTest;
import static org.junit.Assert.*;
#RunWith(SpringJUnit4ClassRunner.class)
#TransactionalDevTest
public class HomeEmployeeServiceImplIntegrationTest {
#Autowired
protected JdbcTemplate jdbcTemplate;
#Autowired
HomeEmployeeService homeEmployeeService;
#Autowired
EmployeeMother empMother;
Employee empJohnDoe;
#Before
public void beforeEachTest() throws Exception {
empJohnDoe = empMother.getEmpJohnDoe();
empMother.insert(empJohnDoe);
}
/**
* You should not be able to add a document with the same name
* <code>uploadDocument</code> should not insert the document if it has the same name
*/
#Test
public void shouldNotBeAbleToAddSameDoc(){
Document doc = new Document();
Long id = jdbcTemplate.queryForObject("select max(td.ID)+1 from tab_documents td", Long.class);
doc.setId(id);
doc.setFileName("SameOldDocument");
homeEmployeeService.uploadDocument(empJohnDoe.getIdentity(), doc);
id = jdbcTemplate.queryForObject("select max(td.ID)+1 from tab_documents td", Long.class);
doc.setId(id);
homeEmployeeService.uploadDocument(empJohnDoe.getIdentity(), doc);
Long docNo = jdbcTemplate.queryForObject("select count(id) from tab_documents td where doc_file_name = '" + doc.getFileName() + "'", Long.class);
if(docNo.compareTo(new Long(2)) == 0){
assertEquals("I was able to add a document twice with the same name!", new Long(1), docNo);
}
else{
assertEquals("Something went wrong when adding two documents with the same name! The document should be added once or twice, but the result is different!", new Long(1), docNo);
}
}
}
I am using spring mvc and I have a service class called UserManager. The class is used to manage a collection of users like add user and delete user from colection. Basicaaly it provides all the information about a collection of users.
This class is used by controllers to access user collection information.
Now the problem is I have to use it as a bean for spring injection. But a bean should have getters and setters only.
So i am confused as to how i implement this class.
here is the code for UserManager
import com.bo.user.UserBO;
/*
* UserManager class is a service class which provides service to Controller for managing the users in the system.
* It has a collection _allUserMap which maintains the users inside the system all through the life of system.
* It manages the addition, deletion and updation of users.
* UserBO is the service which helps UserManager access the users, individually, from Database
*/
#Service
public class UserManager{
#Autowired
private UserBO userBo;
private static Map<Integer,User> _allUserMap = new HashMap<Integer, User>();
/*
* Method populates the _allUserMap
* using userBo
*/
#PostConstruct
public void loadAllUsers(){
Integer id = null;
List<User> _allUserList = userBo.listAllUser();
System.out.println("<--------Initializing all user map--------->");
for(User user : _allUserList){
id = user.getId();
_allUserMap.put(id, user);
}
}
/*
* Adds the user after checking if the user exists
* #param User:Takes the User to add from the Controller
* #Return boolean User added or not
* Beta 1.1 validation for correct user addition form input
*/
public boolean addUser(User user){
boolean userAdded = false;
if (hasUser(user)){
userAdded = false;
}else{
userBo.save(user);
userAdded = true;
}
return userAdded;
}
/*
* Checks if the user is already present
* #Param User
* #Return is user present
*/
private boolean hasUser(User formUser){
boolean isUser = false;
User user = null;
for(Entry<Integer, User> entry: _allUserMap.entrySet()){
user = entry.getValue();
if(user.equals(formUser)){
isUser = true;
}
return isUser;
}
return isUser;
}
/*
* #Param User
* #Return String : message gives what feild is alreay in database
*/
public String matchCredentails(User formUser){
String message = "";
User user = null;
for(Entry<Integer, User> entry: _allUserMap.entrySet()){
user = entry.getValue();
if(user.getEmail().equals(formUser.getEmail())){
message = "Email alreay exists+";
}
if(user.getMobileNumber()== formUser.getMobileNumber()){
message = message + "Mobile number alreay exists+";
}
if(user.getUserName().equals(formUser.getUserName())){
message = message + "UserName alreay exists+";
}
}
return message;
}
}
here is how i am accessing it in controller
#Controller
public class UserController {
//These are the instances of the service providing bean and not the state of the spring controller
#Autowired
private UserManager userManager;
My question is simple... should i make this class a bean. because this class is not a simple pojo by definition.
No, there is absolutely no need for getters and setters here since you are injecting the UserBO field through #Autowired.
The Spring documentation says
A bean is simply an object that is instantiated, assembled and
otherwise managed by a Spring IoC container; other than that, there is
nothing special about a bean [...].
There is no mention of getters/setters and therefore you shouldn't think of them as necessary for beans. Use them when appropriate.