Java Web高级编程(持久化数据)

---JSR是JavaBean Validation的规范  Hibernate Validation是JSR349的参考实现
约束注解可以添加到字段 方法和方法参数上
配置bean

@Bean public LocalValidatorFactoryBean localValidatorFactoryBean () throws ClassNotFoundException { LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); 容器上可能存在多个Bean验证提供者 下面的代码就可以让你去选了 validator.setProvideClass ( Class.forName ( "org.hibernate.validator.HibernateValidate" ) ); return validator; }


要在验证在方法之前执行 需要配置一些bean后处理器
RootContextConfiguration.java 
创建错误代码本地化 
   

 @Bean public MessageSource messageSource () { ReloadableResourceBundleMessageSource messageSource = 
                new ReloadableResourceBundleMessageSource(); messageSource.setCacheSeconds( -1 ); messageSource.setDefaultEncoding( StandardCharsets.UTF_8.name() ); messageSource.setBasenames( "/WEB-INF/i18n/titles", "/WEB-INF/i18n/messages", "/WEB-INF/i18n/errors", "/WEB-INF/i18n/validation" ); return messageSource; } @Bean public LocalValidatorFactoryBean localValidatorFactoryBean () { LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); validator.setValidationMessageSource( this.messageSource() ); return validator; }

    使用方法验证Bean后处理器 
  

@Bean public MethodValidationPostProcessor methodValidationPostProcessor () { MethodValidationPostProcessor processor = 
            new MethodValidationPostProcessor(); processor.setValidator( this.localValidatorFactoryBean() ); return processor; } @Bean public ObjectMapper objectMapper () { ObjectMapper mapper = new ObjectMapper(); mapper.findAndRegisterModules(); mapper.configure( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false ); mapper.configure( DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false ); return mapper; } public class ServletContextConfiguration extends WebMvcConfigurerAdapter { .... @Inject SpringValidatorAdapter validator; @Override 在WebMvcConfigurerAdapter里面定义过 public Validator getValidator () { return this.validator; } ... }

为了保证递归验证 要加@Valid验证

为方法验证配置Spring Bean
1.标注接口 而非实现
接口被认为是实现契约编程的主要形式

@Validated public interface EmployeeService { //在方法参数上使用 
    public void saveEmployee ( @NotNull ( message = "{validate.employeeService.saveEmployee}" ) @Valid Employee employee ); public Employee getEmployee ( //保证id>=1 否则返回一个含指定消息代码的验证错误 
            @Min ( value = 1L, message = "{validate.employeeService.getEmployee.id}" ) long id ); //验证方法返回值不为空 
 @NotNull public List< Employee > getAllEmployees (); }

 在SpringMVC控制器中使用参数验证  
 
  
    @RequestMapping ( value = "/create", method = RequestMethod.POST ) public ModelAndView createEmployee ( Map< String, Object > model, @Valid EmployeeForm form, Errors errors ) { if ( errors.hasErrors() ) { return new ModelAndView( "employee/create" ); } Employee employee = new Employee(); employee.setFirstName( form.getFirstName() ); employee.setLastName( form.getLastName() ); employee.setMiddleName( form.getMiddleName() ); employee.setEmail( form.getEmail() ); try { this.employeeService.saveEmployee( employee ); } catch ( ConstraintViolationException e ) { model.put( "validationErrors", e.getConstraintViolations() ); return new ModelAndView( "employee/create" ); } return new ModelAndView( new RedirectView( "/", true, false ) ); }
@Valid 告诉Spring在执行方法之前验证EmployeeForm
Errors errors 参数的存在告诉Spring吧验证错误传入到方法中 而不是抛出异常 在方法中处理异常 更加优雅 如果存在错误 就将用户列表返回到表单视图中
validation_en_US.properties里面是这样的
validate.employee.firstName=The first name is required.
JSP视图
<body > 
    <h2 ><spring:message code = "title.create.employee" /></h2 > 
    <c:if test = "${validationErrors != null}" > 
        <div class = "errors" > 
            <ul > 
                <c:forEach items = "${validationErrors}" var = "error" > 
                    <li ><c:out value = "${error.message}" /></li > 
                </c:forEach > 
            </ul > 
        </div > 
    </c:if > 
    <form:form method = "post" modelAttribute = "employeeForm" > 
        <form:label path = "firstName" ><spring:message code = "form.first.name" /> 
        </form:label ><br /> 
        <form:input path = "firstName" /><br /> 
        <form:errors path = "firstName" cssClass = "errors" /><br />

        <form:label path = "email" ><spring:message code = "form.email" /> 
        </form:label ><br /> 
        <form:input path = "email" /><br /> 
        <form:errors path = "email" cssClass = "errors" /><br />

        <input type = "submit" value = "Submit" /> 
    </form:form > 
</body >

还可以编写自己的验证约束 比如@email 因为email的验证的正则表达式很烦 可以抽象出来

@Target ( { ElementType.METHOD , ElementType.FIELD , ElementType.ANNOTATION_TYPE , ElementType.CONSTRUCTOR , ElementType.PARAMETER } ) @Retention ( RetentionPolicy.RUNTIME ) @Documented @Constraint ( validatedBy = {} ) @Pattern ( regexp = "^[a-z0-9`!#$%^&*'{}?/+=|_~-]+(\\.[a-z0-9`!#$%^&*'{}?/+=|" + 
        "_~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?)+(\\.[a-z0-9]" + 
        "([a-z0-9-]*[a-z0-9])?)*$", flags = { Pattern.Flag.CASE_INSENSITIVE } ) @ReportAsSingleViolation public @interface Email { String message () default {com.wrox.site.validation.Email.message}""; Class< ? >[] groups () default {}; Class< ? extends Payload >[] payload () default {}; @Target ( { ElementType.METHOD , ElementType.FIELD , ElementType.ANNOTATION_TYPE , ElementType.CONSTRUCTOR , ElementType.PARAMETER } ) @Retention ( RetentionPolicy.RUNTIME ) @Documented static @interface List { Email[] value (); } }

---Spring 负责所有的事务管理和 EntityManager 的创建和关闭!!!

---虽然ORM可以帮你建表 但是实际情况中 永远自己建表

---在Tomcat中创建连接资源

<Resource name="jdbc/DataSourceName" type="javax.sql.DataSource" maxActive="20" maxIdle="5" maxWait="10000" username="root" password="123456" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/databaseName" />

<Resouce>定义将使Tomcat吧连接池DataSource暴露为JNDI资源 这样任何应用程序都可以通过 JNDI查询到该资源

---Hibernate Session代表了一个事务从开始到结束的整个生命周期 不是线程安全 必须在一个线程里面使用 他负责管理实体的状态
从数据库中获取实体的时候 该实体被附着到会话上 并一直作为会话的一部分 通过这种方法 实体的特定属性可以通过延迟加载的方式加载 同样 添加或更新一个实体的时候 这些修改也将被附着到会话上

---

最佳实践

@Access( AccessType.PROPERTY/FIELD ) 使用默认(PROPERTY) 即使用JavaBean 属性 其余的JPA注解大多数都是属性注解 如@Id @Basic @Temporal等
这些注解可以加在field上 或者 公开访问方法上 加在字段上 就要匹配JavaBean属性名 如果加在JavaBean访问方法上 则不需要


最佳实践
使用AccessType.PROPERTY 时就在JavaBean 方法上使用注解 使用 AccessType.FIELD 时 就在字段上添加注解 这将消除混淆 千万不能混用

---使用 Spring Framework 创建 Session Factory
不过在下面的实例中 用了 EntityManager 这个功能上有 Session Factory 的作用 所以没有这么写 !!!EntityManager啊!
RootContextConfiguration.java

@Bean public PersistenceExceptionTranslator persistenceExceptionTranslator() { result new HibernateExceptionTranslator(); } @Bean public HibernateTransactionManager transactionManager() { TransactionManager manager = new TransactionManager(); manager.setSessionFactory( this.sessionFactory() ); result manager; } @Bean public SessionFactory sessionFactory { LocalSessionFactoryBuilder builder = new LocalSessionFactoryBuilder( this.dataSource() ); builder.scanPackage( "..." ) builder.setProperty( ".default_schema", "dbo" ) builder.setProperty( "hibernate.dialect", MySQL5InnoDBDialect,class.getCanonicalName() ); return builder.buildSessionFactory(); }

=============================使用简单实体=================================
---创建实体并将它映射到表
@Entity @Table
@Entity ( name = "PublisherEntity" ) 实体名默认就是类名 注意哦 这个不是表 不过表面默认是
实体名

@Table ( name = "Books", uniqueConstraints = { @UniqueConstraint ( name = "Books_ISBNs", columnNames = { "Isbn" } ) }, indexes = { @Index ( name = "Books_Titles", columnList = "Title" ) } )

这个专用于模式生成 不过不建议使用

---JPA使用实体字段
创建代理键 id 一般用long

@Id @GeneratedValue ( strategy = GenerationType.IDENTITY ) 标识列值的自动生成 @Column ( name = "AuthorId" ) public long getId () { return this.id; }

@Basic
更加显示的方式映射字段 fetch = FetchType.LAZY 表示属性值是从DB中立即读取(默认)还是只在访问时读取映射字段:例如int映射为 INTEGER BIGINT 或者对应的SQL数据类型

---指定类名和其他细节
@Column ( name = "PublisherName", nullable = false )
注意 这里的nullable = false 只用于模式生成 如果没启用 会被忽略

---持久化单元 就是将一个配置和一组实体类从逻辑上组合到一起 该配置将控制着附着到持久化单元的javax.persistence.EntityManager 实例和特定持久化单元中的EntityManager (不太懂 直接用spring好了)
EntityManager 可以很细粒度的吧?foo实体里面的EntityManager 不能控制bar实体?
=========================Spring Framework中使用JPA=========================
---maven依赖

<dependency > 
    <groupId >org.springframework</groupId > 
    <artifactId >spring-orm</artifactId > 
    <version >${spring.framework.version}</version > 
    <scope >compile</scope > 
</dependency >

<dependency > 
    <groupId >org.javassist</groupId > 
    <artifactId >javassist</artifactId > 
    <version >3.18.2-GA</version > 
    <scope >runtime</scope > 
</dependency >

---主要就是EntityManager的依赖注入

---EntityManager 功能等同与 Hibernate ORM的会话和JDBC的连接

---先编写代码 再设计一个支持代码的数据库

---EntityManager在功能上等于Hibernate ORM的会话和JDBC的连接 可以这么写
@PersistenceContext
EntityManager entityManager;
就一般的写@Inject或者@Autowired EntityManager不是线程安全的 而使用@PersistenceContext
意味着 仓库可以在多个线程中使用该实例 并且在后台每个线程都有自己的EntityManager实例 它们将代表你对事务进行管理
使用@PersistenceContext还有一个优点是可以为给定的 EntityManager实例指定一个持久化单元的名称
@PersistenceContext( unitName = "fooUnit" )
EntityManager entityManager;

=========================在 Spring Framework中配置持久化=====================


1.创建或者查找数据源

@Bean public DataSource springJpaDataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setUrl( "jdbc:mysql://localhost/SpringJpa" ); dataSource.setUsername( "root" ) dataSource.setPassword( "123456" ) return dataSource; }

不过 这只能创建一个简单的数据源 该数据源返回的连接只能使用一次 并未提供连接池 所以不能用于生产
可以从属性文件中读取URL 用户名和密码 这样方便修改

@Bean public DataSource springJpaDataSource () { JndiDataSourceLookup lookup = new JndiDataSourceLookup(); return lookup.getDataSource( "jdbc/SpringJpa" ); }

这样写了之后 还要在Tomcat的context.xml中定义数据源

<Resource name="jdbc/DataSourceName" type="javax.sql.DataSource" maxActive="20" maxIdle="5" maxWait="10000" username="root" password="123456" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/databaseName" />

2.在代码中创建或查找持久化单元 并配置Spring将它注入到仓库中 
   

 @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean () { Map< String, Object > properties = new Hashtable<>(); properties.put( "javax.persistence.schema-generation.database.action", "none" ); HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); adapter.setDatabasePlatform( "org.hibernate.dialect.MySQL5InnoDBDialect" ); LocalContainerEntityManagerFactoryBean factory = 
                new LocalContainerEntityManagerFactoryBean(); factory.setJpaVendorAdapter( adapter ); factory.setDataSource( this.springJpaDataSource() ); factory.setPackagesToScan( "com.wrox.site.entities" ); factory.setSharedCacheMode( SharedCacheMode.ENABLE_SELECTIVE ); factory.setValidationMode( ValidationMode.NONE ); factory.setJpaPropertyMap( properties ); return factory; }
创建了一个map用于保存JPA的配置属性 放了一个模式生成属性 接下来创建了一个适配器

3.创建事务管理 使用@Transactional方法得到正确的处理
在 RootContextConfiguration.java 上使用注解@EnableTransactionManagement 激活事务管理和@Transactional方法拦截

@Configuration @EnableScheduling @EnableAsync ( mode = AdviceMode.PROXY, proxyTargetClass = false, order = Ordered.HIGHEST_PRECEDENCE ) @EnableTransactionManagement ( mode = AdviceMode.PROXY, proxyTargetClass = false, order = Ordered.LOWEST_PRECEDENCE ) @ComponentScan ( basePackages = "com.wrox.site", excludeFilters = @ComponentScan.Filter ( { Controller.class , ControllerAdvice.class } ) ) public class RootContextConfiguration implements AsyncConfigurer, SchedulingConfigurer { @Bean public PlatformTransactionManager jpaTransactionManager () { return new JpaTransactionManager( this.entityManagerFactoryBean().getObject() ); } }

默认事务管理将会查找一个名为txManager的bean 然后返回它可以找到的PlatformTransactionManager实现的第一个bean


=============================创建和使用JPA仓库============================


---注入持久化单元
为了在仓库中执行JPA操作 需要一个 EntityManager实例
每个仓库都有一个 EntityManager 实例

public abstract class GenericJpaRepository < I extends Serializable, E extends Serializable > 
        extends GenericBaseRepository< I, E > { @PersistenceContext protected EntityManager entityManager; ... }

注意这里注入的 EntityManager并不是JPA供应商(Hibernate ORM)提供的那个 相反 它是真正的
EntityManager的一个代理实例

---实现标准CRUD操作

@Validated public interface GenericRepository < I extends Serializable, E extends Serializable > { @NotNull Iterable< E > getAll (); E get ( @NotNull I id ); void add ( @NotNull E entity ); void update ( @NotNull E entity ); void delete ( @NotNull E entity ); void deleteById ( @NotNull I id ); }
这里BookRepository可以有个扩展 其他的一些为空就可以了
public interface BookRepository extends GenericRepository< Long, Book > { Book getByIsbn ( String isbn ); }

要在Repository实现类中有下面这两个

protected final Class< I > idClass; protected final Class< E > entityClass;

因为需要访问I和E的类型(Class) 用于执行安全的JPA查询操作 获得类型的最佳方式是在构造函数
中 这个挺复杂的 所以 单抽出来写了一个类
public abstract class GenericBaseRepository < I extends Serializable, E extends Serializable > 
        implements GenericRepository< I, E > { protected final Class< I > idClass; protected final Class< E > entityClass; @SuppressWarnings ( "unchecked" ) public GenericBaseRepository () { Type genericSuperclass = this.getClass().getGenericSuperclass(); while ( !( genericSuperclass instanceof ParameterizedType ) ) { if ( !( genericSuperclass instanceof Class ) ) { throw new IllegalStateException( "Unable to determine type " + 
                        "arguments because generic superclass neither " + 
                        "parameterized type nor class." ); } if ( genericSuperclass == GenericBaseRepository.class ) { throw new IllegalStateException( "Unable to determine type " + 
                        "arguments because no parameterized generic superclass " + 
                        "found." ); } genericSuperclass = ( ( Class ) genericSuperclass ).getGenericSuperclass(); } ParameterizedType type = ( ParameterizedType ) genericSuperclass; Type[] arguments = type.getActualTypeArguments(); this.idClass = ( Class< I > ) arguments[ 0 ]; this.entityClass = ( Class< E > ) arguments[ 1 ]; } }
在GenericJpaRepository中完成所有有趣的JPA操作
public abstract class GenericJpaRepository < I extends Serializable, E extends Serializable > 
        extends GenericBaseRepository< I, E > { @PersistenceContext protected EntityManager entityManager; @Override public Iterable< E > getAll () { CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); CriteriaQuery< E > query = builder.createQuery( this.entityClass ); return this.entityManager.createQuery( query.select( query.from( this.entityClass ) ) ).getResultList(); } @Override public E get ( I id ) { return this.entityManager.find( this.entityClass, id );} @Override public void add ( E entity ) { this.entityManager.persist( entity );} @Override public void update ( E entity ) { this.entityManager.merge( entity );} @Override public void delete ( E entity ) { this.entityManager.remove( entity );} @Override public void deleteById ( I id ) { CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); CriteriaDelete< E > query = builder.createCriteriaDelete( this.entityClass ); this.entityManager.createQuery( query.where( builder.equal( query.from( this.entityClass ).get( "id" ), id ) ) ).executeUpdate(); } }
这里用的是 JPA API 不是很直观
==========================在服务中标记事务范围==============================
最佳实践 吧@Transactional注解标注在具体的类或者类方法上 而不是接口或者接口方法上
Bean验证注解是写在接口上的 是契约 @Transactional是具体的实现细节 不属于契约

配置

@Configuration @EnableTransactionManagement ( mode = AdviceMode.PROXY, proxyTargetClass = false, order = Ordered.LOWEST_PRECEDENCE ) public class RootContextConfiguration implements TransactionManagementConfigurer { @Bean public PlatformTransactionManager jpaTransactionManager () { return new JpaTransactionManager( this.entityManagerFactoryBean().getObject() ); } @Bean public PlatformTransactionManager dataSourceTransactionManager () { return new DataSourceTransactionManager( this.springJpaDataSource ); } @Bean public PlatformTransactionManager annotationDrivenTransactionManager () { return this.jpaTransactionManager(); } }

JpaTransactionManager被用于默认的事务管理器

public SomeService { //使用默认事务管理器 
 @Transactional public void actionOne(); //显示的使用JpaTransactionManager事务管理器 
    @Transactional( "jpaTransactionManager" ) public void actionTwo(); //显示的使用DataTransactionManager事务管理器 
    @Transactional( "dataTransactionManager" ) public void actionThree(); }
使用@Transactional 标注在接口上的话 等同于标注了接口上的所有方法
最佳实践 将注解标注在具体的类或者类方法上 而不是接口或者接口方法上

==========================================================================
主要为了演示BCrypt
DefaultAuthenticationService.java

@Service public class DefaultAuthenticationService implements AuthenticationService { private static final Logger log = LogManager.getLogger(); private static final SecureRandom RANDOM; private static final int HASHING_ROUNDS = 10; static { try { RANDOM = SecureRandom.getInstanceStrong(); } catch ( NoSuchAlgorithmException e ) { throw new IllegalStateException( e ); } } @Inject UserRepository userRepository; @Override @Transactional public UserPrincipal authenticate ( String username, String password ) { UserPrincipal principal = this.userRepository.getByUsername( username ); if ( principal == null ) { log.warn( "Authentication failed for non-existent user {}.", username ); return null; } if ( !BCrypt.checkpw( password, new String( principal.getPassword(), StandardCharsets.UTF_8 ) ) ) { log.warn( "Authentication failed for user {}.", username ); return null; } log.debug( "User {} successfully authenticated.", username ); return principal; } @Override @Transactional public void saveUser ( UserPrincipal principal, String newPassword ) { if ( newPassword != null && newPassword.length() > 0 ) { String salt = BCrypt.gensalt( HASHING_ROUNDS, RANDOM ); principal.setPassword( BCrypt.hashpw( newPassword, salt ).getBytes() ); } if ( principal.getId() < 1 ) { this.userRepository.add( principal ); } else { this.userRepository.update( principal ); } } }
上面使用了BCrypt保护用户密码
==========================================================================

一个小例子


===Repository

public interface BookRepository extends GenericRepository< Long, Book > { Book getByIsbn ( String isbn ); } @Repository public class DefaultBookRepository extends GenericJpaRepository< Long, Book > 
        implements BookRepository { @Override public Book getByIsbn ( String isbn ) { CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); CriteriaQuery< Book > query = builder.createQuery( this.entityClass ); Root< Book > root = query.from( this.entityClass ); return this.entityManager.createQuery( query.select( root ).where( builder.equal( root.get( "isbn" ), isbn ) ) ).getSingleResult(); } }

===Service

public interface BookManager { List< Author > getAuthors (); List< Book > getBooks (); List< Publisher > getPublishers (); void saveAuthor ( Author author ); void saveBook ( Book book ); void savePublisher ( Publisher publisher ); }
 

@Service public class DefaultBookManager implements BookManager { @Inject AuthorRepository authorRepository; @Inject BookRepository bookRepository; @Inject PublisherRepository publisherRepository; @Override @Transactional public List< Author > getAuthors () { return this.toList( this.authorRepository.getAll() ); } @Override @Transactional public List< Book > getBooks () { return this.toList( this.bookRepository.getAll() ); } @Override @Transactional public List< Publisher > getPublishers () { return this.toList( this.publisherRepository.getAll() ); } private < E > List< E > toList ( Iterable< E > i ) { List< E > list = new ArrayList<>(); i.forEach( list::add ); return list; } @Override @Transactional public void saveAuthor ( Author author ) { if ( author.getId() < 1 ) { this.authorRepository.add( author ); } else { this.authorRepository.update( author ); } } @Override @Transactional public void saveBook ( Book book ) {...} @Override @Transactional public void savePublisher ( Publisher publisher ) {...} }

===Controller

@WebController public class BookController { private final Random random; @Inject BookManager bookManager; public BookController () { try { this.random = SecureRandom.getInstanceStrong(); } catch ( NoSuchAlgorithmException e ) { throw new IllegalStateException( e ); } } @RequestMapping ( value = "/", method = RequestMethod.GET ) public String list ( Map< String, Object > model ) { model.put( "publishers", this.bookManager.getPublishers() ); model.put( "authors", this.bookManager.getAuthors() ); model.put( "books", this.bookManager.getBooks() ); return "entities"; } @RequestMapping ( value = "/", method = RequestMethod.POST ) public View add () { Publisher publisher = new Publisher(); publisher.setName( "John Wiley & Sons" ); publisher.setAddress( "1234 Baker Street" ); publisher.setDateFounded( Calendar.getInstance() ); this.bookManager.savePublisher( publisher ); Author author = new Author(); author.setName( "Nicholas S. Williams" ); author.setEmailAddress( "[email protected]" ); author.setGender( Gender.MALE ); this.bookManager.saveAuthor( author ); Book book = new Book(); book.setIsbn( "" + this.random.nextInt( Integer.MAX_VALUE ) ); book.setTitle( "Professional Java for Web Applications" ); book.setAuthor( "Nicholas S. Williams" ); book.setPublisher( "John Wiley & Sons" ); book.setPrice( 59.99D ); this.bookManager.saveBook( book ); return new RedirectView( "/", true, false ); }
}

---在DTO和实体之间转换数据
将这里的Ticket作为DTO(数据传输对象) 并创建TicketEntity用于持久化到数据库中(比如Ticket里 面有一些新特性 JPA还未实现的 就要在TicketEntity里面去换成JPA可以实现的 )
}应用程序用的是Ticket 数据库持久化用的是TicketEntity;

====================使用 Spring Data JPA可以不用自己写Repository=================


解决 比如你要查找一本书 可以根据ISBN 作者 书名 等等查找 每个都要写差不多相似的查找代码 很烦 可以通过 Spring Data 解决 还有分页功能
你要做的就是创建一个接口 Spring Data 可以在运行时自动生成该接口的实现代码

Spring Framework 中常见的程序执行路径
'控制器针对服务接口编写' Spring 将拦截这些接口的调用 并执行所有必要的任务 例如Bean验证 启动事务或者异步调用方法 当服务方法返回时 Spring可能也会执行进一步的Bean验证 并提交或者回滚事务 同样 '访问代码针对仓库接口编写' 在调用仓库仓库方法的时候 Spring 将再次执行所有必要的任务
例如启动事务 当方法返回时 Spring 把所有抛出的异常都转换为DataAccessException

服务中编写业务逻辑 控制器中编写用户界面逻辑 所以仓库中不需要编写什么特别的代码 它只是
持久化和获取实体的公式化代码

---maven依赖

<dependency > 
    <groupId >org.springframework.data</groupId > 
    <artifactId >spring-data-jpa</artifactId > 
    <version >1.5.3.RELEASE</version > 
    <scope >compile</scope > 
    <exclusions > 
        <exclusion > 
            <groupId >org.slf4j</groupId > 
            <artifactId >jcl-over-slf4j</artifactId > 
        </exclusion > 
    </exclusions > 
</dependency >

<dependency > 
    <groupId >org.slf4j</groupId > 
    <artifactId >slf4j-api</artifactId > 
    <version >1.7.7</version > 
    <scope >runtime</scope > 
</dependency >

Spring Data JPA的CrudRepository<T, ID> 集成了许多CRUD操作 '''T表示实体类型 ID标识标识符类型'''

实现类 SimpleJpaRepository

package org.springframework.data.repository; @NoRepositoryBean public interface CrudRepository < T, ID extends Serializable > extends Repository< T, ID > { < S extends T > S save ( S entity ); < S extends T > Iterable< S > save ( Iterable< S > entities ); T findOne ( ID id ); boolean exists ( ID id ); Iterable< T > findAll (); Iterable< T > findAll ( Iterable< ID > ids ); long count (); void delete ( ID id ); void delete ( T entity ); //上面两个delete都是删除单个指定的实体 下面的那个是删除多个实体 void delete ( Iterable< ? extends T > entities ); //删除该类型的所有实体 void deleteAll (); }

使用方式

public interface UserRepository extends CrudRepository< UserPrincipal, Long > { UserPrincipal getByUsername ( String username ); }

org.springframework.data.domain.Sort 封装了一些属性信息 以及指定方式对结果排序
org.springframework.data.domain.Pageable 封装了一个Sort对象和每页中实体的数目以及将要返回的那一页

如果希望仓库提供分页和排序 可以继承 PagingAndSortingRepository< T, ID extends Serializable >

findAll( Sort ) 对T进行排序 返回一个Inerable< T >
findAll( Pageable ) 返回一个Page< T >以Pageable指令指定的边界为限
Pageable封装了一个Sort对象和每页中实体的数目以及将要返回的那一页(都是整数) 在WEB应用程序中 Spring帮我们实现了两个Pageable对象

Spring Data 可以公式化仓库代码啊 太强大了 但是不太透明的感觉啊
!!!===为搜索实体创建查询方法 像下面那个方法 按照约定写 Spring就可以实现


名称应以find...By get...By或者read...By开头 然后紧接着匹配的属性名称 方法参数中提供了该属性的值

public interface BookRepository extends PagingAndSortingRepository< Book, Long > { Book findByIsbn ( String isbn ); Page< Book > findByAuthor ( String author , Pageable instructions ); Page< Book > findByPublisher ( String publisher , Pageable instructions ); }

---查询方法名中可以添加多个属性 并用逻辑操作符分隔它们(Or And)等等

List< Person > findByFirstNameAndLastName ( String firstName , String lastName); List< Person > findByFirstNameOrLastName ( String firstName , String lastName);

还可以用IgnoreCase显示的表示是否忽略大小写 eg, findByFirstNameOrLastNameAllIgnoreCase
这个叫做查询方法语言 非常强大 例如还有 After 和 IsAfter Like Contains StartsWith EndsWith Between IsBetween True IsTrue GreaterThan IsGreaterThan In Null IsNull Exists

---还可以自定义方法实现
---比如在下面的接口自定义了BookRepository的两个方法

public interface BookRepositoryCustomization { public Page< Book > findBookWithQuery ( DynamicQuery query, Pageable p ); public Page< Book > searchBooks( String serachQuery, Pageable p ); }
---然后写一个 BookRepositoryImpl 去实现这个接口

---然后

public interface BookRepository extends PagingAndSortingRepository< Book, Long >, BookRepositoryCustomization{}

在这里只要继承那个自己定义的接口就好 Spring 会自动匹配imp
在使用 Spring Data 查找 BookRepository 的时候 他首先查找相同包中名为 BookRepositoryImpl的类 并将该类实例化和封装为普通的 Spring bean 当调用这些自己写的自定义方法的时候 Spring Data都会把调用委托给该实现(Impl);

还可以自定义所有仓库 极少出现

---使用java配置Spring Data

@Configuration ... @EnableJpaRepositories ( basePackages = "com.wrox.site.repositories", entityManagerFactoryRef = "entityManagerFactoryBean", transactionManagerRef = "jpaTransactionManager" ) ... public class RootContextConfiguration implements AsyncConfigurer, SchedulingConfigurer {...}

---配置 Spring MVC 支持
Spring Data 可以自动将请求参数转为 Spring MVC 处理器参数方法参数 另外ia使用仓库的时候 它可以自动将请求参数和路径变量转换为实体类型

@RequestMapping( "/person/{id}" ) public String viewPerson( @PathVariable( "id" ) Person person) { ... }

注意看这里的@PathVariable( "id" ) Person person 不是跟了@PathVariable( "id" ) long id


Spring Data 可以接受URL的ID 在Person仓库实现中使用 findOne(ID) 获得这一个Person对象
这消除了使用long类型ID方法参数从仓库中获取Person的手动代码;

配置这个的支持 通过@EnableSpringDataWebSupport 注册的bean包含了许多被初始化为默认值的设置 包括参数名称 最大页大小 默认的分页和排序值 在 RestServletContextConfiguration 设置

@Configuration @EnableWebMvc @EnableSpringDataWebSupport @ComponentScan ( basePackages = "com.wrox.site", useDefaultFilters = false, includeFilters = @ComponentScan.Filter ( WebController.class ) ) public class WebServletContextConfiguration extends WebMvcConfigurerAdapter {...} public class RestServletContextConfiguration extends WebMvcConfigurerAdapter { private static final Logger log = LogManager.getLogger(); @Inject ApplicationContext applicationContext; ... @Override public void addArgumentResolvers ( List< HandlerMethodArgumentResolver > resolvers ) { Sort defaultSort = new Sort( new Sort.Order( Sort.Direction.ASC, "id" ) ); Pageable defaultPageable = new PageRequest( 0, 20, defaultSort ); SortHandlerMethodArgumentResolver sortResolver = 
                new SortHandlerMethodArgumentResolver(); // sortParameter defaults to "sort" 
        sortResolver.setSortParameter( "$paging.sort" ); sortResolver.setFallbackSort( defaultSort ); PageableHandlerMethodArgumentResolver pageableResolver = 
                new PageableHandlerMethodArgumentResolver( sortResolver ); pageableResolver.setMaxPageSize( 200 ); pageableResolver.setOneIndexedParameters( true ); // page starts at 1, not 0 // pageProperty defaults to "page" and sizeProperty to "size" // The following is equal to .setPageProperty("$paging.page") and // .setSizeProperty("$paging.size"); 
        pageableResolver.setPrefix( "$paging." ); pageableResolver.setFallbackPageable( defaultPageable ); resolvers.add( sortResolver ); resolvers.add( pageableResolver ); } @Override public void addFormatters ( FormatterRegistry registry ) { if ( !( registry instanceof FormattingConversionService ) ) { log.warn( "Unable to register Spring Data JPA converter." ); return; } // DomainClassConverter adds itself to the registry 
        DomainClassConverter< FormattingConversionService > converter = 
                new DomainClassConverter<>( ( FormattingConversionService ) registry ); converter.setApplicationContext( this.applicationContext ); } ... }

---分页 JSP写 
   

<h3 ><spring:message code = "title.ticketView.comments" /></h3 > 
    <c:choose > 
        <c:when test = "${comments.totalElements == 0}" > 
            <i ><spring:message code = "message.ticketView.noComments" /></i ><br /><br /> 
        </c:when > 
        <c:otherwise > 
            <spring:message code = "message.ticketView.comments.page" /> 
            &nbsp;::&nbsp;<c:forEach begin = "1" end = "${comments.totalPages}" var = "i" > 
            <c:choose > 
                <c:when test = "${(i - 1) == comments.number}" >${i}</c:when > 
                <c:otherwise ><a href = "<c:url value="/ticket/view/${ticket.id}"> 
                            <c:param name="paging.page" value="${i}" /> 
                            <c:param name="paging.size" value="10" /> 
                        </c:url>" >${i}</a ></c:otherwise > 
            </c:choose >&nbsp; 
        </c:forEach ><br /> 
            <c:forEach items = "${comments.content}" var = "comment" > 
                <i ><c:out value = "${comment.customerName}" />&nbsp;</i > (<wrox:formatDate value = "${comment.dateCreated}" type = "both" timeStyle = "short" dateStyle = "medium" />)<br /> 
                <c:out value = "${comment.body}" /><br /><br /> 
            </c:forEach > 
        </c:otherwise > 
    </c:choose >

============================高级搜索=====================================
Spring Data 局限 不能动态查询

============================定义实体间的关系==============================
一对一
实体A最多只能关联到一个实体B 实体B也最多只能关联到一个实体A
如果是双向的 A中有B B中有A 则需要用@OneToOne标记 指定mappedBy特性
该特性告诉JPA提供者在关系的另一端对应"这个"实体的属性是哪个

public class Employee { private EmployeeInfo info; @OneToOne( MappedBy = "employee" , fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true ) public EmployeeInfo getInfo() {...} } public class EmployeeInfo { private Employee employee; @OneToOne( MappedBy = "info" ) public EmployeeInfo getEmployee() {...} }

一对多和多对一关系
更加常见 实际上 无论何时在一个实体中指定了一个一对多关系 通常会在另一个实体中指定一个
对应的多对一关系

@Entity public class Applicant { private Set< Resume > résumés = new HashSet<>(); @OneToMany ( mappedBy = "applicant", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true ) public Set< Resume > getRésumés () { return this.résumés; } public void setRésumés ( Set< Resume > résumés ) {...} } @Entity @Table ( name = "Applicant_Resume" ) public class Resume { ... private Applicant applicant; ... @ManyToOne ( fetch = FetchType.EAGER, optional = false ) @JoinColumn ( name = "ApplicantId" ) public Applicant getApplicant () { return applicant; } public void setApplicant ( Applicant applicant ){...} }

本例中 一个申请人有多个简历 Applicant 中定义了一个简历的Set 该属性允许了持有Applicant的代码块直接浏览申请人的简历 而无须返回到服务或者仓库中
本例中 JPA得知关系是双向的 因为Resume类包含到Applicant的导航属性 只有一对多这边 需要mappedBy

@JoinColumn 指定了连接两个表的列的细节信息(就是外键?)
在 单向的一对多 关系中 它可以只用在关系的一段:@OneToMany这一边(Applicant) 对于这些关系 它表示了另一个实体的表的哪个列包含了当前实体的主键

在 单向的多对一 或者 双向一(多)对多(一) 中 @JoinColumn属于关系的@ManyToOne 这一边 (Resume);

对于这些关系 @JoinColumn 表示当前实体的表的哪个列包含了其他实体的主键,它也代替了该属性的@Column 注解

你可能感兴趣的:(Java Web高级编程(持久化数据))