Hot questions for Spring Repositories

Top 10 Java Open Source / Spring / Spring Repositories

Question:

I have an existing spring 4 project (mvc, jdbc etc) and I tried to port it to spring boot and I can't. (many dependencies troubles, no one can't explain how I can do that). But now I just want to use Spring Data JPA in existing project. That is a main pom dependencies:

<properties>
        <jetty.version>9.3.5.v20151012</jetty.version>
        <spring.version>4.3.12.RELEASE</spring.version>
        <spring.boot.version>1.5.8.RELEASE</spring.boot.version>
        <spring.security.version>4.2.3.RELEASE</spring.security.version>
        <quartz.version>2.2.1</quartz.version>
        <slf4j.version>1.7.5</slf4j.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <hibernate-version>5.3.3.Final</hibernate-version>
    </properties>

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
            <version>${spring.boot.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-validation</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.jboss.logging</groupId>
                    <artifactId>jboss-logging</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-databind</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.fasterxml.jackson.core</groupId>
                    <artifactId>jackson-annotations</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.hibernate</groupId>
                    <artifactId>hibernate-validator</artifactId>
                </exclusion>

            </exclusions>
        </dependency>
    <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.0.5.RELEASE</version>
        </dependency>
<dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate-version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate-version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.0-api</artifactId>
            <version>1.0.1.Final</version>
        </dependency>

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>4.2.1.RELEASE</version>
        </dependency>

I have existing configuration, that I completed with entityManagerFactory Bean (that used in all examples). DBConfig:

@Configuration
@ComponentScan("ru.testproject.*")
@EnableTransactionManagement
@EnableJpaRepositories("ru.testproject.*")
public class DatabaseConfig {

    @Value("${postgresql.address}")
    String address;
    @Value("${postgresql.database}")
    String database;
    @Value("${postgresql.user}")
    String user;
    @Value("${postgresql.password}")
    String password;
    @Value("${db.type}")
    String dbType;

    @Bean
    public DataSource dataSource() {

        if (DB_TYPE_POSTGRESQL.equalsIgnoreCase(dbType)) {
            return postresqlDataSource();
        } else {
            return derbyDataSource();
        }

    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);

        return dataSourceTransactionManager;
    }

    private HikariDataSource derbyDataSource() {

        try {
            String appHome = System.getProperty(Property.APP_HOME);

            Path path = Paths.get(appHome, DATABASE_NAME);
            String databaseName = path.toString();

            databaseName = StringUtils.replaceChars(databaseName, '\\', '/');

            Class.forName(DB.DB_DRIVER_DERBY).newInstance();

            EmbeddedDataSource embeddedDataSource = new EmbeddedDataSource();
            String dbName = databaseName;

            embeddedDataSource.setDatabaseName(dbName);
            embeddedDataSource.setCreateDatabase("create");

            embeddedDataSource.setUser("application");
            embeddedDataSource.setPassword("");

            HikariConfig config = new HikariConfig();
            config.setDataSource(embeddedDataSource);
            config.setMaximumPoolSize(10);
            config.setAutoCommit(true);

            HikariDataSource hikariDataSource = new HikariDataSource(config);

            return hikariDataSource;
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new IllegalStateException("error connection Derby");
        }

    }

    private HikariDataSource postresqlDataSource() {

        if (StringUtils.isBlank(address)
                || StringUtils.isBlank(database)
                || StringUtils.isBlank(user)
                || StringUtils.isBlank(password)) {
            throw new IllegalStateException("error params Postgresql");
        }

        HikariConfig config = new HikariConfig();
        String jdbcUrl = MessageFormat.format(POSTGRESQL, address, database);
        config.setJdbcUrl(jdbcUrl);
        config.setUsername(user);
        config.setPassword(password);
        config.setMaximumPoolSize(10);
        config.setAutoCommit(false);

        return new HikariDataSource(config);
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws SQLException {

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("ru.testproject.hibernate.*");
        factory.setDataSource(dataSource());
        factory.afterPropertiesSet();
        factory.setPersistenceUnitName("test");
        return factory;
    }

}

Derby database used for tests. But main database type is PostgreSQL with HikariCP. I'm already add HQL support in project and it works ok. But when I try to add JAP repositories support, I have many problems. I have entity:

@Entity
@Table(name = "users", schema = "public", catalog = "test")
public class Users {
    private int id;
    private String username;
    private String password;
    private String email;

    @Id
    @Column(name = "id", nullable = false)
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Basic
    @Column(name = "username", nullable = false, length = 64)
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Basic
    @Column(name = "password", nullable = false, length = 64)
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Basic
    @Column(name = "email", nullable = false, length = 128)
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Users that = (Users) o;
        return id == that.id &&
                Objects.equals(username, that.username) &&
                Objects.equals(password, that.password) &&
                Objects.equals(email, that.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, username, password, email);
    }
}

And Repository:

@Repository
public interface UsersRepository extends CrudRepository<Users, Long> {

    @Query("select b from Users b where b.username = :username")
    Users findByName(@Param("username") String username);
}

it's just a test repository extends CrudRepository. For using that repository I've created service with implementation:

@Service
public interface UserService {

    void delete(long id);
    Users getByName(String name);
    Optional<Users> getById(Long id);
    Users editUsers(Users user);
}

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UsersRepository usersRepository;

//    @PersistenceContext(unitName = "test")
//    private EntityManager em;

    @Override
    public void delete(long id) {
        usersRepository.deleteById(id);
    }

    @Override
    public Optional<Users> getById(Long id) {
        return usersRepository.findById(id);
    }

    @Override
    public Users getByName(String name) {
        return usersRepository.findByName(name);
    }

    @Override
    public Users editUsers(Users user) {
        return usersRepository.save(user);
    }
}

And I'm using repository like that:

@Autowired
    private UserService service;
...
Users entry = service.getById(1L).get();

that is all. I don't use Spring Boot (as said above) and start as application failed with error:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in ru.testproject.config.DBConfig: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean]: Factory method 'entityManagerFactory' threw exception; nested exception is java.lang.NoSuchFieldError: parallelCapableClassLoaderAvailable
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:587)

If I comment @EnableJpaRepositories annotation, I have the following error:

Unsatisfied dependency expressed through field 'userServiceImpl'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userServiceImpl': Unsatisfied dependency expressed through field 'usersRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'ru.testproject.db.hibernate.repository.UsersRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

In second scenario repository is null, Certainly - JPA repo's disabled.. Please, help! What I'm doing wrong and how can I make it works? I can't resolve my problem with google and Internet lessions. Maybe because I don't understand what is the root problem.


Answer:

It seems you are not adding the test package ru.testproject.db.hibernate.repository in component scan.

Question:

I would like to set more than one LDAP repositories with Spring LDAP. My aim is to create or update objects in all repositories at the same time.

I use LdapRepository Spring interface and I think that isn't possible for now.

I wonder if I can create my own LdapRepository extending the Spring one but I have no idea how to start.

This my configuration :

@Configuration
@EnableLdapRepositories("com.xxx.repository.ldap")
@PropertySource("classpath:ldap.properties")
public class LdapConfiguration {

    @Autowired
    Environment ldapProperties;

    @Bean
    public LdapContextSourceCustom contextSourceTarget() {
        LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
        ldapContextSource.setUrl(ldapProperties.getProperty("ldap.url"));
        ldapContextSource.setBase(ldapProperties.getProperty("ldap.base"));
        ldapContextSource.setUserDn(ldapProperties.getProperty("ldap.userDn"));
        ldapContextSource.setPassword(ldapProperties.getProperty("ldap.password"));
        ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap.truststore"));

        return ldapContextSource;
    }

    @Bean
    public LdapTemplate ldapTemplate(){
        return new LdapTemplate(contextSourceTarget());
    }
}

And to be complete, one repository:

public interface LdapUserRepository extends LdapRepository<LdapUser> {

}

Any idea how to do it ?

Thanks in advance for any help.


Answer:

I don't known if I understood correctly but here is what we did:

  1. Global configuration class

    @Bean("odm")
    public ObjectDirectoryMapper odm() {
        return new DefaultObjectDirectoryMapper();
    };
    
  2. First LDAP configuration class

    @Configuration
    @PropertySource("classpath:ldap-one.properties")
    public class LdapOneConfiguration {
    
        @Autowired
        Environment ldapProperties;
    
        @Bean(name = "contextSourceOne")
        public LdapContextSourceCustom contextSourceLdapOneTarget() {
            LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
            ldapContextSource.setUrl(ldapProperties.getProperty("ldap-one.url"));
            ldapContextSource.setBase(ldapProperties.getProperty("ldap-one.base"));
            ldapContextSource.setUserDn(ldapProperties.getProperty("ldap-one.userDn"));
            ldapContextSource.setPassword(ldapProperties.getProperty("ldap-one.password"));
            ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap-one.truststore"));
    
            return ldapContextSource;
        }
    
        @Bean(name = "ldapTemplateOne")
        public LdapTemplate ldapOneTemplate(@Qualifier("contextSourceOne") LdapContextSourceCustom contextSource) {
            return new LdapTemplate(contextSource);
        }
    
        @Bean(name = "ldapUserRepoOne")
        public LdapUserRepository ldapUserRepositoryOne(@Qualifier("ldapTemplateOne") LdapTemplate ldapTemplate,
            @Qualifier("odm") ObjectDirectoryMapper odm) {
            return new LdapUserRepository(ldapTemplate, odm);
        }
    
        @Bean(name = "ldapFamilyRepoOne")
        public LdapFamilyRepository ldapFamilyRepositoryOne(@Qualifier("ldapTemplateOne") LdapTemplate ldapTemplate,
            @Qualifier("odm") ObjectDirectoryMapper odm) {
            return new LdapFamilyRepository(ldapTemplate, odm);
        }
    }
    
  3. Second LDAP configuration class

    @Configuration
    @PropertySource("classpath:ldap-two.properties")
    public class LdapTwoConfiguration {
        @Autowired
        Environment ldapProperties;
    
        @Bean(name = "contextSourceTwo")
        public LdapContextSourceCustom contextSourceLdapTwoTarget() {
            LdapContextSourceCustom ldapContextSource = new LdapContextSourceCustom();
            ldapContextSource.setUrl(ldapProperties.getProperty("ldap-two.url"));
            ldapContextSource.setBase(ldapProperties.getProperty("ldap-two.base"));
            ldapContextSource.setUserDn(ldapProperties.getProperty("ldap-two.userDn"));
            ldapContextSource.setPassword(ldapProperties.getProperty("ldap-two.password"));
            ldapContextSource.setKeyStoreFile(ldapProperties.getProperty("ldap-two.truststore"));
    
            return ldapContextSource;
        }
    
        @Bean(name = "ldapTemplateTwo")
        public LdapTemplate ldapTwoTemplate(@Qualifier("contextSourceTwo") LdapContextSourceCustom contextSource) {
            return new LdapTemplate(contextSource);
        }
    
        @Bean(name = "ldapUserRepoTwo")
        public LdapUserRepository ldapUserRepositoryTwo(@Qualifier("ldapTemplateTwo") LdapTemplate ldapTemplate,
            @Qualifier("odm") ObjectDirectoryMapper odm) {
            return new LdapUserRepository(ldapTemplate, odm);
        }
    
        @Bean(name = "ldapFamilyRepoTwo")
        public LdapFamilyRepository ldapFamilyRepositoryTwo(@Qualifier("ldapTemplateTwo") LdapTemplate ldapTemplate,
            @Qualifier("odm") ObjectDirectoryMapper odm) {
            return new LdapFamilyRepository(ldapTemplate, odm);
        }
    
    }
    
  4. LdapUser repository

    public class LdapUserRepository extends SimpleLdapRepository<LdapUser> {
    
        public LdapUserRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm) {
            super(ldapOperations, odm, LdapUser.class);
        }
    }
    
  5. LdapFamily repository

    public class LdapFamilyRepository extends SimpleLdapRepository<LdapFamily> {
    
        public LdapFamilyRepository(LdapOperations ldapOperations, ObjectDirectoryMapper odm) {
            super(ldapOperations, odm, LdapFamily.class);
        }
    }
    
  6. LdapUser service (same for LdapFamily service)

    @Service
    public class LdapUserServiceImpl implements LdapUserService {
    
        @Autowired
        private ApplicationContext appContext;
    
        private LdapUserRepository uniqueLdapUserRepo;
    
        private List<LdapUserRepository> ldapUserRepoList;
    
        @PostConstruct
        private void setUniqueRepo() {
            uniqueLdapUserRepo = appContext.getBeansOfType(LdapUserRepository.class).values().iterator().next();
            ldapUserRepoList = new ArrayList<>(appContext.getBeansOfType(LdapUserRepository.class).values());
        }
    
        @Override
        public LdapUser getUser(String uid) {
            return uniqueLdapUserRepo.findOne(query().where("uid").is(uid));
        }
    
        @Override
        public void saveUser(LdapUser user) {
            for(LdapUserRepository repo: ldapUserRepoList){
                repo.save(user);
        }
    }
    

    }

We deleted the auto configuration of LDAP repo:

@EnableLdapRepositories(basePackages = "com.afklm.paul.repository.ldap", ldapTemplateRef = "ldapTwoTemplate")

Thanks ryan2049 for your help.

Question:

I create custom method in Spring repository

@Transactional
@Modifying(clearAutomatically = true)
@Query("UPDATE Order co SET co.orderStatus =:status WHERE co.orderStatus =:newStatus")
int updateOrderStatus(@Param("status") OrderStatus status, @Param("newStatus") OrderStatus newStatus);

and call it in service:

log.info("started");
int i = orderRepository.updateOrderStatus(OrderStatus.CREATED, OrderStatus.PENDING);
log.info("updates: " + i);

And I see in logs:

INF 15:43:45.102 started INF 15:43:46.035 updates: 0

In DB I have 3 rows with CREATED status. I have not err and rows not update.

@Column(name = "order_status")
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus;

findAllByOrderStatus(OrderStatus.CREATED) get 3 rows

Answer:

Your query is the other way around:

    @Transactional
    @Modifying(clearAutomatically = true)
    @Query("UPDATE Order co SET co.orderStatus =:newStatus WHERE co.orderStatus =:status")
    int updateOrderStatus(@Param("status") OrderStatus status, @Param("newStatus") OrderStatus newStatus);

Question:

I've got a class which contains an atttribute of java.time.LocalDateTime type.

public class MyClass{
    // ...
    private LocalDateTime fecha;
    // ...
}

I'm using Spring Data repositories. What I want to accomplish is to query entities according to a date:

@Service
public interface IRepository extends CrudRepository<MyClass, UUID> {
    // ...
    public void deleteByFecha(LocalDate fecha);
    // ...
}

But this does not work, as an exception is thrown:

org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [2016-10-05] did not match expected type [java.time.LocalDateTime (n/a)];

So the question is how can I query MyClass in database by fecha but with a LocalDate?

EDIT Just in case somebody faces the same issue, I've come up with one solution: modify the Repository's method so that it looks as follows:

import org.springframework.transaction.annotation.Transactional;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
// ...

@Service
public interface IRepository extends CrudRepository<MyClass, UUID> {

    @Transactional
    @Modifying
    @Query("DELETE FROM MyClass mtc WHERE YEAR(mtc.fecha)=?1 AND MONTH(mtc.fecha)=?2 AND DAY(mtc.fecha)=?3")
    public void deleteByFecha(Integer year, Integer month, Integer day);

}

Answer:

Try this (not tested):

public interface IRepository extends CrudRepository<MyClass, UUID> {
    // ...
    default void delByFecha(LocalDate fecha) {

        deleteByFechaBetween(fecha.atStartOfDay(), fecha.plusDays(1).atStartOfDay());

    }

    void deleteByFechaBetween(LocalDateTime from, LocalDateTime to);
    // ...
}

Question:

I try to create a custom repository by following this tutorial: https://www.baeldung.com/spring-data-jpa-method-in-all-repositories

My app build fail with error:

NoSuchBeanDefinitionException: No qualifying bean of type 'com.example.demo.dao.ExtendedStudentRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

My full source code: https://github.com/sesong11/springjpa-custom-repo

There are plenty of similar questions, but none of them fixed for me. Perhaps it's Spring issue on current version 2.1.1, or I missed some configuration.


Answer:

To get your test working, you have to do the following:

1) Replace the incorrect definition of basePackages in StudentJPAH2Config to com.example.demo.dao, or better remove it as redundant:

@Configuration
@EnableJpaRepositories(repositoryBaseClass = ExtendedRepositoryImpl.class)
public class StudentJPAH2Config {
}

2) Also replace basePackages in @ComponentScan and @EntityScan in DemoApplication class to com.example.demo.dao and com.example.demo.entity. Or better removed those and @EnableTransactionManagement annotations at all:

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

3) Add a no-argument constructor to the entity Student.

4) Correct your test class - use @DataJpaTest to test DAO layer and import your StudentJPAH2Config configuration:

@RunWith(SpringRunner.class)
@DataJpaTest
@Import(StudentJPAH2Config.class)
public class ExtendedStudentRepositoryIntegrationTest {
   //...
}

With these corrections, I've run your test successful.