zoukankan      html  css  js  c++  java
  • 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底层异常的事务可以回滚
            }
        }  




  • 相关阅读:
    ----localStorage的同步与异步----
    ----vue2.0实现别人通过ip访问自己运行的项目----
    ----vue之搜索框与防抖函数的封装----
    ----vue项目打包之浏览器存在缓存问题----
    ----vue组件name的作用小结----
    ----HTML5本地储存--利用storage事件实时监听Web Storage----
    ---- vue之filter ----
    ----vue项目配置环境----
    ----git-ssh 配置和使用----
    python『学习之路03』装饰器
  • 原文地址:https://www.cnblogs.com/xingqi/p/3867770.html
Copyright © 2011-2022 走看看