2014-07-25 10 views
6

Sto creando un sito Web utilizzando Spring MVC e per la persistenza sto utilizzando Spring Data JPA con Hibernate 4 come provider JPA. La convalida viene attualmente gestita con Hibernate Validator. Ho un problema per cui i miei validatori vengono chiamati due volte e non riesco a capire perché. Il motivo principale per cui questo è un problema è perché la seconda volta, le dipendenze non vengono autowired nel validatore e sto ricevendo un'eccezione di puntatore nullo. che segue è la sequenza di chiamate che portano al fallimento:JSR303 convalidatori personalizzati chiamati due volte

  1. La scheda di iscrizione è presentata e il primo la NotDefaultSectValidator si chiama e completa con successo per il campo 'whereDidYouHearAboutUs' per l'oggetto utente.
  2. L'UniqueUsernameValidator viene chiamato next e viene completato correttamente per la convalida del campo 'username'.
  3. Il metodo 'addUserFromForm' sul controller si avvia e non rileva errori nell'oggetto bindingResults.
  4. Il metodo 'addUser' viene quindi chiamato sulla classe UserService. Questo metodo raggiunge la riga 'userRepository.save (utente);' ma non esegue mai la riga 'print.ln' immediatamente dopo. Superando questa linea si ritorna al punto di interruzione 'NotDefaultSectValidator'. Questo si completa per la seconda volta e reinserisco il secondo validatore 'UniqueUsernameValidator'. Qui ottengo un'eccezione di puntatore nullo perché, per qualche motivo, Spring non riesce a eseguire l'Autowire nel DAO per la seconda volta.

Qualcuno può far luce sul perché i validatori sono chiamati due volte e in particolare, perché scavalcando linea 'userRepository.save (utente);' torna in questi validatori?

Molte grazie

Ecco la mia classe user.java

Il metodo rilevante nel mio controller di registrazione:

@RequestMapping(value = "/register", method = RequestMethod.POST) 
public String addUserFromForm(@Valid User user, 
     BindingResult bindingResult, RedirectAttributes ra) { 
    if (bindingResult.hasErrors()) { 
     return "user/register"; 
    } 
    userService.addUser(user); 

    // Redirecting to avoid duplicate submission of the form 
    return "redirect:/user/" + user.getUsername(); 
} 

La mia classe di servizio:

package com.dating.service.impl; 

import javax.transaction.Transactional; 

import org.joda.time.LocalDate; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 
import org.springframework.stereotype.Service; 

import com.dating.domain.Role; 
import com.dating.domain.User; 
import com.dating.repository.RoleRepository; 
import com.dating.repository.UserRepository; 
import com.dating.repository.specification.UserSpecifications; 
import com.dating.service.UserService; 

@Service 
public class UserServiceImpl implements UserService { 

    @Autowired 
    private UserRepository userRepository; 

    @Autowired 
    private RoleRepository roleRepository; 

    @Transactional 
    @Override 
    public void addUser(User user) { 
     user.setJoinDate(new LocalDate()); 
     user.setEnabled(true); 
     Role role = roleRepository.findByName(Role.MEMBER); 
     if (role == null) { 
      role = new Role(); 
      role.setName(Role.MEMBER); 
     } 
     user.addRole(role); 
     BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); 
     user.setPassword(encoder.encode(user.getPassword())); 
     userRepository.save(user); 
     System.out.println("User Saved"); 
    } 

    @Override 
    public User getUserByUsername(String username) { 
     return userRepository.findByUsername(username); 
    } 

    @Override 
    public Iterable<User> getAllUsers() { 
     return userRepository.findAll(); 
    } 

    @Override 
    public void updateDetails(User user) { 
     userRepository.save(user); 
    } 

    @Override 
    public Iterable<User> lastNameIsLike(String searchTerm) { 
     return userRepository.findAll(UserSpecifications 
       .lastNameIsLike(searchTerm)); 
    } 
} 

Il mio no tDefaultSelect validatore:

package com.dating.validator; 

import javax.validation.ConstraintValidator; 
import javax.validation.ConstraintValidatorContext; 
import com.dating.annotation.NotDefaultSelect; 

public class NotDefaultSelectValidator implements 
     ConstraintValidator<NotDefaultSelect, String> { 
    @Override 
    public void initialize(NotDefaultSelect constraint) { 

    } 

    @Override 
    public boolean isValid(String selectedValue, ConstraintValidatorContext ctx) { 
     if (selectedValue == null) { 
      return false; 
     } 
     if (selectedValue.equals("") || selectedValue.equals("0") 
       || selectedValue.equalsIgnoreCase("default") 
       || selectedValue.equalsIgnoreCase("please select")) { 
      return false; 
     } 
     return true; 
    } 

} 

mio uniqueUsername validatore:

package com.dating.validator; 

import javax.validation.ConstraintValidator; 
import javax.validation.ConstraintValidatorContext; 

import org.springframework.beans.factory.annotation.Autowired; 

import com.dating.annotation.UniqueUsername; 
import com.dating.repository.UserRepository; 

public class UniqueUsernameValidator implements 
     ConstraintValidator<UniqueUsername, String> { 

    @Autowired 
    private UserRepository userRepository; 

    @Override 
    public void initialize(UniqueUsername constraint) { 

    } 

    @Override 
    public boolean isValid(String username, ConstraintValidatorContext ctx) { 
     if (username == null || userRepository.findByUsername(username) == null) { 
      return true; 
     } 
     return false; 
    } 

} 

mio UserRepository:

package com.dating.repository; 

import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 
import org.springframework.data.repository.CrudRepository; 

import com.dating.domain.User; 

//Spring Data JPA Marker interfaces being extended for automatic CRUD repository creation 
public interface UserRepository extends CrudRepository<User, Long>, JpaSpecificationExecutor<User> { 

    //Automatic query creation from method name 
    public User findByUsername(String username); 
} 

Infine la mia persistenza-context.xml file di

<!-- Data source properties --> 
<util:properties id="dataSourceSettings" location="classpath:datasource.properties" /> 

<!-- Pooled data source using BoneCP --> 
<bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" 
    destroy-method="close"> 
    <property name="driverClass" value="#{dataSourceSettings['jdbc.driverClass']}" /> 
    <property name="jdbcUrl" value="#{dataSourceSettings['jdbc.url']}" /> 
    <property name="username" value="#{dataSourceSettings['jdbc.username']}" /> 
    <property name="password" value="#{dataSourceSettings['jdbc.password']}" /> 
    <property name="idleConnectionTestPeriodInMinutes" value="60" /> 
    <property name="idleMaxAgeInMinutes" value="240" /> 
    <property name="maxConnectionsPerPartition" value="30" /> 
    <property name="minConnectionsPerPartition" value="10" /> 
    <property name="partitionCount" value="3" /> 
    <property name="acquireIncrement" value="5" /> 
    <property name="statementsCacheSize" value="100" /> 
    <property name="releaseHelperThreads" value="3" /> 
</bean> 

<!-- JPA entity manager factory bean --> 
<bean id="entityManagerFactory" 
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="dataSource" ref="dataSource" /> 
    <property name="packagesToScan" value="com.dating.domain" /> 
    <property name="jpaVendorAdapter"> 
     <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> 
    </property> 
    <property name="jpaProperties"> 
     <props> 
      <prop key="hibernate.dialect">#{dataSourceSettings['hibernate.dialect']}</prop> 
      <prop key="hibernate.hbm2ddl.auto">#{dataSourceSettings['hibernate.hbm2ddl.auto']} 
      </prop> 
      <prop key="hibernate.show_sql">#{dataSourceSettings['hibernate.show_sql']}</prop> 
      <prop key="hibernate.format_sql">#{dataSourceSettings['hibernate.format_sql']}</prop> 
      <prop key="hibernate.use_sql_comments">#{dataSourceSettings['hibernate.use_sql_comments']} 
      </prop> 
     </props> 
    </property> 
</bean> 

<tx:annotation-driven transaction-manager="transactionManager" /> 

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 
    <property name="entityManagerFactory" ref="entityManagerFactory" /> 
</bean> 

<context:annotation-config /> 

<jpa:repositories base-package="com.dating.repository"/> 
+2

Riduci il codice qui. –

+2

Prima l'istanza, se convalidata da Spring, viene convalidata dalla sospensione prima del salvataggio. O fissa i tuoi validatori in modo che le dipendenze vengano iniettate (o tirate) correttamente o disabiliti la convalida per hibernate/jpa. –

+0

Si noti inoltre che l'autowiring di un campo privato lo rende solo iniettabile tramite riflessione. Dovresti fornire anche un argomento setter o costruttore da iniettare. Non tutte le iniezioni devono essere fatte da Spring sai. – Bart

risposta

5

Forse la seconda convalida io s fatto in ibernazione quando si invia il bean al datastore. Per disattivarlo aggiungere questo alla tua persistence.xml:

<property name="javax.persistence.validation.mode" value="none"/> 

https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/configuration.html dice:

Per impostazione predefinita, Bean Validation (e Hibernate Validator) è attivato.Quando un'entità viene creata, aggiornata (e facoltativamente cancellata), viene convalidata prima di essere inviata al database. Lo schema del database generato da Hibernate riflette anche i vincoli dichiarati sull'entità.

È possibile mettere a punto che, se necessario:

AUTO: se Bean Validation è presente nel classpath, CALLBACK e DDL vengono attivati.

CALLBACK: le entità sono convalidate in fase di creazione, aggiornamento e cancellazione. Se non è presente alcun provider di convalida Bean, viene generata un'eccezione al momento dell'inizializzazione.

DDL: (non standard, vedere di seguito) gli schemi di database sono entità convalidate in fase di creazione, aggiornamento e cancellazione. Se non è presente alcun provider di convalida Bean, viene generata un'eccezione al momento dell'inizializzazione.

NONE: Bean Validation non viene utilizzato affatto

Il primo è ovviamente fatta dal controller primavera a causa di annotazioni @Valid.

+0

Fantastico, grazie a questo ho risolto il mio problema. Solo per curiosità, potresti sapere perché il UserRepository era nullo durante la seconda chiamata anche se l'ho impostato per essere Autowired? È perché il validatore è stato chiamato al di fuori del contesto di primavera? Come suggeriresti di risolvere questo problema? –

+0

Probabilmente hai ragione: sei fuori dal contesto primaverile. Tuttavia, a mio parere, non penso che sia una buona idea fare TUTTE le operazioni di persistenza durante la convalida del vincolo del bean. Devi chiedere ad alcuni utenti più esperti se è buono o no. A mio avviso, viene fatto solo per verificare cose come valori nulli, formati di dimensioni, ortografia, forse alcuni checksum e non una logica applicativa. Ma questa è solo la mia sensazione. Pensa anche ai test di junit, alla gestione delle transazioni e ad altri problemi che sicuramente avrai con questo tipo di design. –

+0

@MarekRaszewski come verrebbero inseriti i messaggi di ibernazione in BindingResult? –