最近小项目的一些记录(一)

Published on:

最近花了2个星期的时间做了一个小网站,用来统计部门同事发布的博客情况。需求比较简单,做这个项目的时候就顺便把以前学到的东西整合到了一起,从前端到后台,从编码到部署(“全栈工程师”?呵呵),虽然事情比较琐碎但也学到了不少东西,下面就记录一下开发过程中遇到的一些问题。

技术栈

这里先列举一下项目用到的一些技术,其实这些东西就是自己的工具箱,要慢慢丰富,要及时更新,这样才能做出来好东西。

  • Spring4 MVC
  • Hibernate orm
  • Spring Data JPA
  • AngularJS(Javascript MVW Framework)
  • Semantic UI(CSS Framework)
  • Velocity(for mail)
  • Gradle
  • SAE(Sina App Engine)

Spring JPA

项目遇到不少的问题都来自JPA,也有一部分原因是由于SAE的MySql数据库是读写分离的两个库,所以要配置多个数据源才能在上面正常读写数据。
JPA有个好处就是操作数据库时不用写太多代码,不用像以前一样写一个接口再写一个实现,只需要一个接口就可以完成基本的操作了,如果有特殊的操作则可以通过标签的方式来写sql。

Spring JPA配置多个persistence-unit(或多个数据源)

  • 首先增加JPA的多persistence-unit的管理Bean。

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="persistenceUnitManager"
    class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
    <property name="persistenceXmlLocation" value="classpath:META-INF/persistence.xml"/>
    <!-- comment dataSourceLooup to use jndi -->
    <property name="dataSourceLookup">
    <bean class="org.springframework.jdbc.datasource.lookup.BeanFactoryDataSourceLookup"/>
    </property>
    </bean>
  • 然后配置多套DataSource,EntityManagerFactory,TransactionManger和jpa:repository,这里要重点注意jps:repository的配置也要有多套,否则启动就会报No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 2的错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <!--write persistence unit config-->
    <bean id="writeJpaVendor"
    class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="showSql" value="true"/>
    <property name="generateDdl" value="true"/>
    </bean>

    <bean id="writeDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource " destroy-method="close">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/deblog"/>
    <property name="user" value="root"/>
    <property name="password" value="root"/>
    <property name="acquireIncrement" value="1"/>
    <property name="initialPoolSize" value="5"/>
    <property name="maxPoolSize" value="20"/>
    <property name="minPoolSize" value="5"/>
    <property name="maxStatements" value="100"/>
    <property name="testConnectionOnCheckout" value="true"/>
    </bean>

    <bean id="writeEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitManager" ref="persistenceUnitManager" />
    <property name="persistenceUnitName" value="mainPersistenceUnit"/>
    <property name="jpaVendorAdapter" ref="writeJpaVendor" />
    <property name="loadTimeWeaver">
    <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
    <property name="jpaDialect">
    <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
    </property>
    </bean>

    <bean id="writeTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="writeEntityManagerFactory"/>
    <qualifier value="writeEm" />
    <property name="jpaDialect">
    <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
    </property>
    </bean>

    <jpa:repositories base-package="com.github.dba.repo.write"
    entity-manager-factory-ref="writeEntityManagerFactory"
    transaction-manager-ref="writeTransactionManager" />

    <tx:annotation-driven transaction-manager="writeTransactionManager"/>
  • 在persistence.xml文件中增加多个unit,这里以一个unit为例,多个的话只要persistence-unit的name不一样就可以了。下面的例子引用了之前的datasource的配置,可以不需要再配置一次jdbc信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <persistence-unit name="mainPersistenceUnit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <non-jta-data-source>writeDataSource</non-jta-data-source>
    <class>com.github.dba.model.Blog</class>
    <class>com.github.dba.model.DepGroup</class>
    <class>com.github.dba.model.DepMember</class>
    <class>com.github.dba.model.BlogView</class>
    <properties>
    <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
    <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/deblog" />
    <property name="javax.persistence.jdbc.user" value="root" />
    <property name="javax.persistence.jdbc.password" value="root" />

    <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
    <property name="hibernate.show_sql" value="true" />
    <property name="hibernate.hbm2ddl.auto" value="update" />
    </properties>
    </persistence-unit>

Spring JPA动态查询

  • 首先在model类中增加一个静态方法,用来生成本次查询的动态条件。
    • 下面的例子假设depGroup, website, startDate, endDate都可能有值。
    • where中的”=”,”<”,”>”可以在CriteriaBuilder中找到相应的方法,还有比如like等。
    • 如果是嵌套对象的话,比如Blog对象包含Author对象,要对比Author对象的值,则可以用这种方式来取值: root.<Author>get("author").<String>get("groupName")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public static Specification<Blog> querySpecification(final String depGroup, final String website,
final String startDate, final String endDate) {
return Specifications.where(new Specification<Blog>() {
@Override
public Predicate toPredicate(Root<Blog> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.conjunction();

if (!Strings.isNullOrEmpty(depGroup) && !"*".equals(depGroup)) {
predicate.getExpressions().add(
cb.equal(root.<Author>get("author").<String>get("groupName"), depGroup));
}

if (!Strings.isNullOrEmpty(website) && !"*".equals(website)) {
predicate.getExpressions().add(
cb.equal(root.<String>get("website"), website));
}

if (!Strings.isNullOrEmpty(startDate)) {
try {
long time = DbaUtil.parseTimeStringToLong(startDate, PAGE_DATE_FORMAT);
predicate.getExpressions().add(cb.ge(root.<Long>get("time"), time));
} catch (ParseException e) {
throw new RuntimeException(format("%s parse to date error:", startDate));
}
}

if (!Strings.isNullOrEmpty(endDate)) {
try {
long time = DbaUtil.parseTimeStringToLong(endDate, PAGE_DATE_FORMAT);
predicate.getExpressions().add(cb.le(root.<Long>get("time"), time));
} catch (ParseException e) {
throw new RuntimeException(format("%s parse to date error:", endDate));
}
}

return predicate;
}
});
}
  • 写好了动态查询条件后,就要把它放到查询语句里面了,比如要查询所有数据,示例如下,例子还加了一个对时间的排序条件。
    1
    2
    3
    List<Blog> blogs = blogReadRepository.findAll(
    Blog.querySpecification(depGroup, website, startDate, endDate),
    new Sort(Sort.Direction.DESC, "time"));

嵌套对象

这个可能跟JPA没有多大关系,更多是跟Hibernate有关,但都属于db层面的,就写在一起了。

比如有张表是Blog,这样用Hibernate-orm对应到程序就有一个Blog类,如果Blog属性比较多的话,后续就会变成了一个大类。我们想在数据库只对应一张表的情况下,可以对应到程序的多个类,比如Blog类下面有个Author的类,要怎么做呢?可以用@Embedded标签来解决这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity(name = "blogs")
public class Blog {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Embedded
private Author author;
}

@Embeddable
public class Author {
@Basic
private String name;
}

分离成多个对象的话,有个好处就是可以在不同的model添加不同的逻辑计算,避免把所有逻辑都放在一个类里面,这也是面向对象设计时要考虑的一个问题。但数据库始终只对应一张表,操作简单。

下一篇: 最近小项目的一些记录(二)

赞赏

Comments