NO END FOR LEARNING

Writing blog if you feel tired | 学海无涯 苦写博客

JPA中的多对多关系

| Comments

在关系型数据库中,表与表之间的关系是通过外键来实现,而其中最常见的关系有两种:一对多和多对多。

在面向对象的世界,对象与对象之间的关系是通过从源对象到目标对象的对象引用实现。关系是一个对象含有的其他对象或者对象的集合,而在关系型数据库中,关系要么通过定义另一张表的外键建立,要么通过中间表建立。

在Java或者JPA的世界里,所有的关系都是单向性的,一个源对象保有目标对象的索引,并不能保证目标对象和源对象一样包含源对象的索引。这个关系型数据库就不一样了,在关系型数据库中,表之间的关系通过外键来实现,查询语句通过这个外键,可以进行正向和反向的查询。

ManyToMany多对多关系是关系型数据库中一种非常常见的表关系。

在JPA中,如果要实现ManyToMany关系,常常是利用的一种方式是中间表。所有的ManyToMany关系都需要一个JoinTable,在这个JoinTable中,通过JoinColumns定义了两个外键,一个外键来指向源表的主键,另一个外键指向目标表的主键,通常JoinTable的主键是两个外键的组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
public class Employee {
  @Id
  @Column(name="ID")
  private long id;
  ...
  @ManyToMany
  @JoinTable(
      name="EMP_PROJ",
      joinColumns={@JoinColumn(name="EMP_ID", referencedColumnName="ID")},
      inverseJoinColumns={@JoinColumn(name="PROJ_ID", referencedColumnName="ID")})
  private List<Project> projects;
  .....
}

双向ManyToMany

虽然在数据库中ManyToMany总是双向的,但是在对象模型中定义双向关系时,需要定义映射的发起方(管理方)和映射的接收方。发起方需要定义JoinTable,接收方则需要定义mappedBy。如果不使用mappedBy,则Jpa的实现者会认为,这种多对多关系是两个独立定义的关系,就会有重复的两行记录插入到JoinTable中。

1
2
3
4
5
6
7
8
9
10
@Entity
public class Project {
  @Id
  @Column(name="ID")
  private long id;
  ...
  @ManyToMany(mappedBy="projects")
  private List<Employee> employees;
  ...
}

双向关系的共同毛病

需要应用程序自己去维护它们(对象)之间的双向关系,否则就不同步(out of sync)。在写set方法或者,多对多关系中的add方法时,需要注意将自己的引用设置到包含的集合对象中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Employee {
    private List phones;
    ...
    public void addPhone(Phone phone) {
        this.phones.add(phone);
        if (phone.getOwner() != this) {
            phone.setOwner(this);
        }
    }
    ...
}

public class Phone {
    private Employee owner;
    ...
    public void setOwner(Employee employee) {
        this.owner = employee;
        if (!employee.getPhones().contains(this)) {
            employee.getPhones().add(this);
        }
    }
    ...
}

多对多关系的管理方的选择

多对多关系的管理方的定义取决于JoinTable的定义方,至于由哪一边作为管理方,取决于业务的关系,所以并不固定。但是必须要理解的时,它们之间建立关系的方式(即谁管理谁)。举个例子,User和Group,这是一个常见的多对多关系,当你将管理方定义在Group这边时,你要做的是将User添加到Group,然后保存Group。而不能将Group添加给User,然后保存User。

应用级联

由某一方来管理对象的操作,需要应用级联属性,来说明是否将级联操作传递到对应的实体类上。如果你在存储一个对象时,该对象关联到另一个对象,而它们之间的关系没有使用级联,则会抛出异常,告诉你,需要先存储关联对象。

级联操作的类型有6种,分别是ALL,PERSIST,MERGE,REMOVE,REFRESH,DETACH。

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
public enum CascadeType {

    /** Cascade all operations */
    ALL,

    /** Cascade persist operation */
    PERSIST,

    /** Cascade merge operation */
    MERGE,

    /** Cascade remove operation */
    REMOVE,

    /** Cascade refresh operation */
    REFRESH,

    /**
     * Cascade detach operation
     *
     * @since Java Persistence 2.0
     *
     */
    DETACH
}
1
@ManyToMany(cascade = CascadeType.ALL)

参考资料:
1.http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany
2.http://stackoverflow.com/questions/4935095/jpa-hibernate-many-to-many-cascading

Gradle深入与实战(五)自定义插件

| Comments

利用Gradle做构建,必然逃不掉Gradle的插件的使用,即便是最简单的Java或Groovy的应用都需要使用Java插件或者Groovy插件。

Gradle插件的作用就是将会被重复利用的逻辑打包,这样就可以在不同的项目中重复的使用。比如在上一节中实现的集成测试任务,就可以打包到插件中,然后在其他的工程中使用,而不需要重复的写相同的任务。

Gradle提供了三种写插件的方式:

1.直接在build.gradle文件中写插件,然后直接使用,不好的地方很明显,不能在该build.gradle脚本之外的位置(其他脚本或者工程)中使用。

2.将插件写在项目的rootProjectDir/buildSrc/src/main/groovy包下,Gradle会负责编译和放置到classpath,虽然可以多个gradle脚本中使用,但是不能在其他工程中使用。

3.一个独立的插件工程,很明显,这是最常见的实现方式,因为可以被任何脚本或者工程使用。

为了节省时间,我们直接进入到最常见的实现方式:实现一个独立的插件工程

这是一个Groovy工程

我们知道,Gradle项目是基于Groovy语言开发的,所以插件功能必然是一个Groovy工程,构建创建一个build.gradle文件。

build.gradle
1
2
3
4
5
6
7
8
9
10
11
apply plugin: 'idea'
apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

按照Groovy插件推荐的目录结构建立好下面结构的目录,你可以忽略java那一级

src/main/java
src/main/resources
src/main/groovy
src/test/java
src/test/resources
src/test/groovy

利用Plugin接口实现插件

然后,我们写一个Groovy类,让它实现Plugin接口,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package me.zeph.gradle.plugin

import me.zeph.gradle.extension.HelloExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

class HelloPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.task('hello') << {
            println 'hello plugin'
        }
    }
}

这里,我们通过project的task方法实现了一个名字是hello的task,里面打印了一句话。这个任务很简单,现在我们来增加一点点复杂度。记不记得大部分插件在使用之后,除了提供一些列的task,还提供了许多的closure(闭包),可以通过这些闭包传递一些参数进去。那么,这是怎么是实现的呢?很简单,利用project提供的扩展。

扩展的使用

定义一个名字是HelloExtension的Groovy类(名字其实无所谓叫什么,而且居然不需要实现任何的接口):

1
2
3
4
5
package me.zeph.gradle.extension

class HelloExtension {
    String message;
}

改变一些插件的实现:

build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package me.zeph.gradle.plugin

import me.zeph.gradle.extension.HelloExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

class HelloPlugin implements Plugin<Project> {

    @Override
    void apply(Project project) {
        project.extensions.add('hello', HelloExtension)
        project.task('hello') << {
            println project.hello.message
        }
    }
}

project.extensions.add(‘hello’, HelloExtension),这段代码将HelloExtension添加到project的extensions中,于是task就可以通过project.hello.message来获取。是不是很简单?

告诉别人这是个插件:插件id

那么,功能部分都写完了,怎么样让其他构件脚本知道这是一个插件能?配置META-INF。

在resources目录下建立这样一个目录结构:/resources/META-INF/gradle-plugins

然后在这里建立一个名字是me.zeph.hello.properties的Property文件,文件里的内容是:

me.zeph.hello.properties
1
implementation-class=me.zeph.gradle.plugin.HelloPlugin

这个Property文件的命名并不是随意定义的,名字的作用是定义该插件的id,什么意思?说白了就是apply时使用的名字。如下:

1
apply plugin: 'me.zeph.hello'

至于里面的内容,我就不解释了,一眼就看明白了。

使用生成的插件

到这里,一个独立的插件工程就完成了,实验一把!!

运行gredlew clean assemble,将生成的jar文件,拷贝到其他的项目目录中(这里没有upload到仓库,所以直接文件形式引入依赖)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath fileTree(dir: 'libs', include: '*.jar')
    }
}

apply plugin: 'me.zeph.hello'

hello {
    message = 'hello gradle plugin'
}

然后运行gradlew hello,就可以看到hello任务的执行。

总结,其实实现一个Gradle的独立插件工程,从建立工程的角度还是比较简单的,关键在如何通过Groovy实现插件,已经理解插件的api。

参考资料:

1.https://gradle.org/docs/current/userguide/custom_plugins.html