Spring Data JPA

  • 配置相关bean
    • 数据源
      <context:property-placeholder ignore-resource-not-found="true" location="classpath:jdbc-${spring.profiles.active}.properties"/> 
      <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
          <!-- 基本属性 url、user、password -->
          <property name="url" value="${ds.url}"/>
          <property name="username" value="${ds.username}"/>
          <property name="password" value="${ds.password}"/>
          <!-- 配置初始化大小、最小、最大 -->
          <property name="initialSize" value="5"/>
          <property name="minIdle" value="5"/>
          <property name="maxActive" value="50"/>
          <!-- 配置获取连接等待超时的时间 -->
          <property name="maxWait" value="60000"/>
          <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
          <property name="timeBetweenEvictionRunsMillis" value="60000"/>
          <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
          <property name="minEvictableIdleTimeMillis" value="300000"/>
          <property name="validationQuery" value="SELECT 'x'"/>
          <property name="testWhileIdle" value="true"/>
          <property name="testOnBorrow" value="false"/>
          <property name="testOnReturn" value="false"/>
          <!-- 打开removeAbandoned功能 -->
          <property name="removeAbandoned" value="true"/>
          <property name="removeAbandonedTimeout" value="1800"/>
          <property name="logAbandoned" value="true"/>
          <!-- 打开PSCache,并且指定每个连接上PSCache的大小,mysql 不使用 -->
          <property name="poolPreparedStatements" value="false"/>
          <!-- 配置监控统计拦截的filters -->
          <property name="filters" value="stat"/>
          <!-- 慢查询sql打印 -->
          <property name="connectionProperties" value="druid.stat.slowSqlMillis=200;druid.stat.logSlowSql=true"/>
      </bean>  
    • 实体扫描并生成Repository
      <bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
          <property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect"/>
      </bean> 
      <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
          <property name="dataSource" ref="dataSource"/>
          <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
          <property name="packagesToScan" value="com.qingbo.sapling"/>
          <property name="jpaProperties">
              <props>
                  <!-- 命名规则 My_NAME->MyName 自动更新数据库表结构(仅适用于开发阶段,正式运行后最好是手动维护数据库表结构) -->
                  <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                  <prop key="hibernate.hbm2ddl.auto">update</prop>
                  <prop key="hibernate.show_sql">true</prop>
                  <prop key="hibernate.format_sql">true</prop>
              </props>
          </property>
      </bean> 
      <!-- 自动寻找Repository接口并提供实现类以支持@Autowired注入,包名支持通配符,repository-impl-postfix="impl" -->
      <jpa:repositories base-package="com.qingbo.sapling"/>    
    • 事务,校验,测试时需要加事务注解@Transactional,网站运行需要配置OpenEntityManagerInViewFilter 
      <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
          <property name="entityManagerFactory" ref="entityManagerFactory"/>
      </bean>  
      <!-- proxy-target-class为true时@Transactional需要注解实现类的方法,而false则使用java的基于接口的代理 -->

      <tx:annotation-driven  proxy-target-class="true"/>
       
      <!-- 自动搜索providerClass,也可指定org.hibernate.validator.HibernateValidator ,需提供资源文件classpath:ValidationMessages.properties  -->
      <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>  
    • maven插件任务配置pom.xml,(这个是可选的,会自动生成很多类,可用于支持 QueryDslPredicateExecutor 查询接口 )
      <!--querydsl-->
      <plugin>
          <groupId>com.mysema.maven</groupId>
          <artifactId>apt-maven-plugin</artifactId>
          <version>1.1.0</version>
          <configuration>
              <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
          </configuration>
          <dependencies>
              <dependency>
                  <groupId>com.mysema.querydsl</groupId>
                  <artifactId>querydsl-apt</artifactId>
                  <version>3.3.1</version>
              </dependency>
          </dependencies>
          <executions>
              <execution>
                  <phase>generate-sources</phase>
                  <goals>
                      <goal>process</goal>
                  </goals>
                  <configuration>
                      <outputDirectory>target/generated-sources/annotations</outputDirectory>
                  </configuration>
              </execution>
          </executions>
      </plugin>  
    • 网站配置web.xml
      <filter>  
            <filter-name>sessionFilter</filter-name>  
            <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>  
            <init-param>  
                <param-name>singleSession</param-name>  
                <param-value>false</param-value>  
            </init-param>
      </filter>  
      <filter-mapping>  
            <filter-name>sessionFilter</filter-name>  
            <url-pattern>*.html</url-pattern>  
      </filter-mapping>  
  • dao持久层接口Repository,数据库表实体类也在这一层
    • 核心查询接口:可以实现以下接口,也可以注解@RepositoryDefinition编写自己的接口
      CrudRepository,提供基于键值ID和实体Entity的增Create删Delete改Update查Retrieve,T findOne(ID id);
      PagingAndSortingRepository,提供分页和排序功能,Page<T> findAll(Pageable pageable)
      JpaRepository,增加批量操作和刷新输出功能,void flush();
      JpaSpecificationExecutor,复杂查询接口(持久层查询分页),Page<T> findAll(Specification<T> spec, Pageable pageable);
      QueryDslPredicateExecutor ,querydsl查询接口支持,
      使用样例:
      interface UserRepository extends PagingAndSortingRepository<User, Long> //普通分页排序,可以添加自己的findBy命名查询接口
      , JpaSpecificationExecutor<User>  //支持Criteria复查查询
      , QueryDslPredicateExecutor<User>   //支持querydsl方式查询
    • 简单查询方法命名规范,以下方法只需定义接口无需自己写实现代码,
      支持方法前缀findBy+find+readBy+read+getBy+get,
      支持属性名及AndOr拼接,
      支持属性操作Between+LessThan+GreaterThan+IsNull+[Is]NotNull+Like+NotLike+OrderBy+Desc+Not+In+NotIn(支持集合数组或可变参数),
      支持属性嵌套findByAddressZipCode(ZipCode)可查询address.zipCode(支持findByAddress_ZipCode以避免歧义addressCode.zip),

      其他判断词还有:findByStartDateAfter,findByEndDateBefore,findByNameStartingWith,findByAgeOrderByName,findByActiveTrue
      支持排序和分页:Page<User> findByName(String name, Pageable pageable);//也支持Sort sort参数
      List<User> findByName(String, Sort sort);//支持返回List结果,避免构建Page而进行其他查询(count计算总数等),同时需要再定义count接口
      支持扩展自定义接口:extends MyInterfaceCustom, Repository,然后实现类MyInterfaceCustomImpl,注入@PersistenceContext EntityManager em; 自定义接口方法查库
      支持自定义全局接口:MyRepository extends JpaRepository,MyRepositoryImpl extends SimpleJpaRepository implements MyRepository,然后定义MyRepositoryFactoryBean extends JpaRepositoryFactoryBean提供MyRepositoryFactory extends JpaRepositoryFactory,在getTargetRepository方法里返回new MyRepositoryImpl,最后在repositories配置factory-class即可
    • 命名查询,这些最好少用,代码里面最好不要有sql类语句
      • 配置文件META-INF/orm.xml
        <named-query name="User.findByLastname">
            <query>select u from User u where u.lastname = ?1</query>
        </named-query> 
      • 注解实体类查询
        @Entity @NamedQuery(name = "User.findByEmailAddress", query = "select u from User u where u.emailAddress = ?1")
        public class User {
        }  
        public interface UserRepository extends JpaRepository<User, Long> {
            List<User> findByLastname(String lastname);//spring会优先查找NamedQuery,其次分析方法名
        }  
      • 注解接口方法,支持简单统计sql查询语句(复杂条件统计还得研究JPA查询条件)
        public interface UserRepository extends JpaRepository<User, Long> {
            @Query("select u from User u where u.emailAddress = ?1")
            User findByEmailAddress(String emailAddress);
            @Query(value="select count(ts_user_id) from ts_user group by register_type limit ?1, ?2", nativeQuery=true)
            List registerPage(int offset, int length);  
        }  
      • 使用命名参数
        public interface UserRepository extends JpaRepository<User, Long> {
            @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
            User findByLastnameOrFirstname(@Param("lastname") String lastname, @Param("firstname") String firstname);
        }  
      • 定义更新方法,注意这里参数序号也许比上面命名参数更方便
        public class Snippet {
            @Modifying @Transactional @Query("update User u set u.firstname = ?1 where u.lastname = ?2")
            int setFixedFirstnameFor(String firstname, String lastname);
        }  
    • 复杂查询条件
      Specification,复杂查询条件封装,Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); 
      Predicate,条件组合
      Root,查询根可以有多个
      CriteriaQuery,提供得到查询根的方法
      CriteriaBuilder,构件查询对象,CriteriaQuery<Object> createQuery();
    • 简单查询代码,直接使用Root+CriteriaQuery+CriteriaBuilder
      Specification<TsUser> spec = new Specification<TsUser>() {
          @Override
          public Predicate toPredicate(Root<TsUser> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
              List<Predicate> predicates = new ArrayList<Predicate>();
              if(StringUtils.isNotBlank(userSearch.getUserName())) predicates.add(cb.like(root.<String>get("userName"), "%"+userSearch.getUserName()+"%"));
              query.where(predicates.toArray(new Predicate[0]));
              return null;
          }
      };  
    • 封装查询代码,使用Criteria封装多个条件并返回Predicate判断
      Specification<TsUser> spec = new Specification<TsUser>() {
          @Override
          public Predicate toPredicate(Root<TsUser> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
              Criteria<TsUser> c = new Criteria<TsUser>();
              c.add(Restrictions.like("userName", userSearch.getUserName()));// UserSearch是前端封装的查询表单对象
      c.add(Restrictions.between("createdAt", DateUtil.parse(costSearch.getDateFrom()), DateUtil.parse(costSearch.getDateTo())));//区间查询 
              c.add(Restrictions.like("tsUserreg.userAlias", userSearch.getUserAlias()));//查询其他相关表字段
              c.add(Restrictions.eq("status", userSearch.getStatus()));// 精确查询
              return c.toPredicate(root, query, cb);
          }
      };  
      //再次简化,原来Criteria封装并没有使用Root+CriteriaQuery+CriteriaBuilder,所以添加spec()函数生成Specification查询条件
      Criteria<TsUser> c = new Criteria<>();
      c.add(Restrictions.like("userName", userSearch.getUserName()));
      c.add(Restrictions.like("tsAccount.paymentAccountId", userSearch.getPaymentAccountId()));
      c.add(Restrictions.eq("tsUserreg.roleIds", userSearch.getRole()));//实际上应该封装find_in_set函数,暂时仅支持单一角色
      Pageable pageable = new PageRequest(pager.getCurrentPage()-1, pager.getPageSize(), Direction.DESC"id");
      Page<User> users = userService.userPage(c.spec(), pageable);  
      Restrictions.java片段
      public static SimpleExpression eq(String fieldName, Object value) {  
          if(StringUtils.isEmpty(value))return null;  
          return new SimpleExpression (fieldName, value, Operator.EQ);  
      } 
      public static BetweenExpression between(String fieldName, Object value, Object value2) {
          if(StringUtils.isEmpty(value) && StringUtils.isEmpty(value2)) return null;
          return new BetweenExpression(fieldName, value, value2);
      }   
      AbstractExpression,计算查询路径(支持属性嵌套,例如address.zipCode)
      public abstract class AbstractExpression implements Criterion {
         Path getExpression(Root<?> root, String fieldName) {
              Path expression = null;  
              if(fieldName.contains(".")){  
                  String[] names = StringUtils.split(fieldName, ".");  
                  expression = root.get(names[0]);  
                  for (int i = 1; i < names.length; i++) {  
                      expression = expression.get(names[i]);  
                  }  
              }elseexpression = root.get(fieldName); }
              return expression;
          }
      } 
      SimpleExpression,支持Equal、Like、LessThan等条件
      public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder builder) {  
          Path expression = getExpression(root, fieldName); 
          switch (operator) {  
          case EQ:  
              return builder.equal(expression, value);  
          case LIKE:  
              return builder.like((Expression<String>) expression, "%" + value + "%");   
          }  
      }  
      BetweenExpression,支持区间查询
      public Predicate toPredicate(Root<?> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
          Path expression = getExpression(root, fieldName);
          if(StringUtils.isEmpty(value)) {
              return builder.greaterThanOrEqualTo(expression, (Comparable)value);
          }else if(StringUtils.isEmpty(value2)) {
              return builder.lessThanOrEqualTo(expression, (Comparable)value2);
          }else {
              return builder.between(expression, (Comparable)value, (Comparable)value2);
          }
      }   
      Criteria,
      public class Criteria<T> implements Specification<T>{  
          private List<Criterion> criterions = new ArrayList<Criterion>();  
          public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {  
              if (!criterions.isEmpty()) {  
                  List<Predicate> predicates = new ArrayList<Predicate>();  
                  for(Criterion c : criterions){  
                      predicates.add(c.toPredicate(root, query,builder));  
                  }  
                  // 将所有条件用 and 联合起来  
                  if (predicates.size() > 0) {  
                      return builder.and(predicates.toArray(new Predicate[predicates.size()]));  
                  }  
              }  
              return builder.conjunction();  // 没有条件时相当于true
          }  
          public void add(Criterion criterion){  
              if(criterion!=null){  
                  criterions.add(criterion);  
              }  
          }  
          public Specification<T> spec() {
              return new Specification<T>() {
                  public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                      return Criteria.this.toPredicate(root, query, cb);
                  }
              };
          }  
      }  
    • querydsl查询接口支持,查询根、字段都对象化了(querydsl maven-plugin任务生成的类),不像Criteria封装有实体类属性名称硬编码
      QTask $ = QTask.task;
      BooleanExpression titleStartWith = $.title.startsWith("Study");
      BooleanExpression descEndWith = $.description.endsWith("org/");
      Iterable<Task> result = taskDao.findAll(titleStartWith.and(descEndWith));
  • domain领域层,领域对象是业务核心层,与持久层松散耦合(需要领域对象与表实体类之间转换);
    • 查询分页,domain领域层接口只是衔接service业务层和dao持久层,将数据库表对象TsUser转换为领域对象User
      public Page<User> userPage(Specification<TsUser> spec, Pageable pageable) {
          Page<TsUser> page = userRepository.findAll(spec, pageable);
          List<User> content = new ArrayList<User>();
          Iterator<TsUser> iterator = page.iterator();
          while(iterator.hasNext()) {
              content.add(User.formUser(iterator.next()));
          }
          return new PageImpl<User>(content, pageable, page.getTotalElements());
      }  
    • Audit审计,记录实体创建和修改的管理账户,
      • domain领域对象需实现接口Auditable<U, ID>(U是审计类型,通常是用户名或用户实体类),void setCreatedBy(final U createdBy);
      • orm.xml注册监听器
        <persistence-unit-metadata>
            <persistence-unit-defaults>
                <entity-listeners>
                    <entity-listener class="org.springframework.data.jpa.domain.support.AuditingEntityListener" />
                </entity-listeners>
            </persistence-unit-defaults>
        </persistence-unit-metadata> 
      •  激活AuditorAware的自定义实现,其方法T getCurrentAuditor();返回的就是审计的用户(可以是String用户名或User用户对象,是setCreatedBy(U)的参数)
        <jpa:auditing auditor-aware-ref="yourAuditorAwareBean" /> 
  • service业务层,面向前端页面封装表单对象,定义接口并调用domain层来实现(需要表单对象与领域对象之间转换),建议每个web项目都有各自的service层(如front-service,admin-service等)
    • 查询分页,需要构建Specification查询条件,将领域对象User转换为页面表单对象UserItem
      Specification<TsUser> spec = new Specification<TsUser>() {
          @Override
          public Predicate toPredicate(Root<TsUser> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
              Criteria<TsUser> c = new Criteria<TsUser>();
              c.add(Restrictions.like("userName", userSearch.getUserName()));
              c.add(Restrictions.eq("tsUserreg.status", userSearch.getStatus()));
              return c.toPredicate(root, query, cb);
          }
      };  
      Page<User> users = userService.userPage(spec, pageable);  
      List<UserItem> userItems = new ArrayList<UserItem>();
      for (User user : users.getContent()) {
          UserItem userItem = UserItem.formUserItem(user);
          userItems.add(userItem);
      }  
  • web控制层(按项目划分如front-web,admin-web等)
    • 前端开发人员负责内容
      页面:jsp或vm
      控制:Controller
      业务接口及页面对象:front-service,UserItem等,自定义接口并提供假数据展示到页面
    • 后台开发人员负责内容
      负责domain和dao层设计开发(如果前端开发人员面向domain层开发,则形成紧耦合,会影响项目进度)
      负责实现front-service等服务项目的接口实现,提供数据库数据
  • 数据校验和异常处理
    • web层数据校验:spring会自动校验@Valid参数并将结果存入BindingResult对象,UserItem等对象需要使用@NotNull等校验注解
      UserController.userUpdate(@Valid UserItem, BindingResult bindingResult, Model model){ if(bindingResult.hasErrors()) return NOT_VALID_PAGE; }
    • service+domain数据校验
      建议用AOP实现,全局抛校验异常,然后转异常处理逻辑
    • 异常处理,
      @Service("excetionResolver")
      public class ExceptionResolver extends DefaultHandlerExceptionResolver {
          @Override
          protected ModelAndView doResolveException(HttpServletRequest request,
                  HttpServletResponse response, Object handler, Exception ex) {
              if(ex instanceof AuthorizationException) {//未授权或过期则拒绝访问,校验失败异常可类似处理
                  if(request.getRequestURI().contains("admin/home.html")) {
                      return new ModelAndView("redirect:login.html");
                  }
                  return new ModelAndView("admin/denied");
              }else if(ex instanceof AuthenticationException) {//错误密码超过5次等,认证错误
                  return new ModelAndView("redirect:login.html");
              }
              //return new ModelAndView("admin/error");//其他业务异常,显示错误页面
              return null;//不认识的异常直接抛出,保证service底层异常的事务可以回滚
          }
      }  




posted @ 2014-07-25 13:02  xlongwei  阅读(4464)  评论(0编辑  收藏  举报
xlongwei