2017. szeptember 15., péntek

Using @ElementCollection in Spring JPA queries


In my domain model, I had a list of Strings to be stored. As it is a typical reason of using element collections, I decided to do so.

Using element collection in the domain model has the following advantages:
  • The domain model remains simple. No additional type, 
  • The connected list is bounded to the "master" entity. You do not need to take care of the maintenance of the elements by defining cascade type
  • The data was static, so only read from database was needed, therefore creating a one to many connection seemed not necessary. 
  • It is even possible to define an enumeration as type of the element collection. It gives the possibility to store only predefined values in the field.
My entity class looks like this:

@SuppressWarnings("serial")
@Entity
@Table(name = "SMART_HOME_SUBSCRIPTION")
@Data
public class SmartHomeSubscription implements Serializable {

 @Id
 @Column(name = "ID", unique = true, nullable = false, length = 36)
 private String id;

 @Column(name = "NAME", nullable = false, unique = true, length = 20)
 private String name;

 @ElementCollection(fetch = FetchType.EAGER)
 @CollectionTable(name = "ADDON", joinColumns = @JoinColumn(name = "SMART_HOME_SUBSCRIPTION_ID"))
 @Column(name = "NAME")
 private Set<String> addonNames = new HashSet<>();

 @ElementCollection(targetClass = AllowedFunction.class, fetch = FetchType.EAGER)
 @CollectionTable(name = "ALLOWED_FUNCTION", joinColumns = @JoinColumn(name = "SMART_HOME_SUBSCRIPTION_ID"))
 @Column(name = "NAME")
 @Enumerated(EnumType.STRING)
 private Set<AllowedFunction> allowedFunctions = new HashSet<>();
}

So how it is possible to use these element collections in queries in Spring JPA repository?

As you can see, the items of the element collection with String and annotated type can be used simple as Strings in the query. Equal and IN operator can be used to them such as for normal String fields.    


 @Query(value = "SELECT count(s) FROM SmartHomeSubscription s INNER JOIN s.allowedFunctions f INNER JOIN s.addonNames a WHERE f = :allowedFunction AND a IN :addonNames")
 public int getNumberOfAddonsValidForFunction(@Param("allowedFunction") AllowedFunction allowedFunction,
   @Param("addonNames") List<String> addonNames);










2017. szeptember 14., csütörtök

Create Spring service used only in test environment

Create Spring service used only in test environment

In my project, we use three different environments for running the application.
  • Production
  • SIT (system integration)
  • Local development
In the production environment thee is a third party system, that authenticates the user and automatically adds a header value to the request.

I wanted to implement a feature, which always requires the authentication information. So I needed to add mocked information into the REST request header in SIT and Local development environment.

I used a Service to process the request, and created additional subclasses for Local development and SIT environments. The subclasses must not be annotated as service. Otherwise you get exception at startup, marking that your service class in not unique.

The configuration of my local development environment looks like this:

@Configuration
@EnableScheduling
@EnableAsync
@EnableAspectJAutoProxy
@EnableTransactionManagement
@Profile("backend_localdev")
@EnableConfigurationProperties(DaoConfiguration.class)
public class BackendConfigurationLocalDev extends BackendConfiguration {

 @SuppressWarnings("unused")
 @Autowired
 private DaoConfiguration daoConfiguration;

 @Bean(value = "crbService")
 public CrbService getCrbService() {
  return new CrbServiceLocalDev();
 }
}


In order to add header values to the REST request, I implemented a HttpServletRequestWrapper class.

My service for local development looks like this:


public class CrbServiceLocalDev extends CrbService {

 // Logger instance
 private static final Logger log = LoggerFactory.getLogger(CrbServiceLocalDev.class);

 private static final String HARDCODED_CRB_USER_STATUS_HEADER_VALUE = "{\"key\":\"value\"}";

 @Override
 public CrbUserInfo createCrbUserInfo(HttpServletRequest request) {
  log.warn("Entering CrbServiceLocalDev.createCrbUserInfo(). Must be used only for local development");

  HttpServletRequestWrapper wrapper = new LocalDevHttpServletRequestWrapper(request);
  return super.createCrbUserInfo(wrapper);
 }

 private class LocalDevHttpServletRequestWrapper extends HttpServletRequestWrapper {

  public LocalDevHttpServletRequestWrapper(HttpServletRequest request) {
   super(request);
  }

  @Override
  public String getHeader(String name) {

   if (StringUtils.isBlank(name)) {
    return super.getHeader(name);
   }

   final String value = getRequest().getParameter(name);
   if (!StringUtils.isBlank(value)) {
    return value;
   }

   if (MobileAppCrbUserStatus.HEADER_PARAM_X_CRB_USERSTATUS.equals(name)) {
    log.warn("Hardcoded userstatus header value is returned: {}", HARDCODED_CRB_USER_STATUS_HEADER_VALUE);
    return HARDCODED_CRB_USER_STATUS_HEADER_VALUE;
   }

   return super.getHeader(name);
  }
 }

}