传统艺能增删改查开始了,学完这个,就可以写基础的应用了。

两个基础概念

SessionFactory,这是Hibernate通过配置文件创建的一个对象,每个Web应用里只有一个。可以理解为像连接池一样,这个SessionFactory 相当于是一个Session池。

Session,与Web开发的Session不同,每个Session对象里边装着一个JDBC连接,是用来增删改查的对象,生存时间很短,需要的时候就从SessionFactory中获取。

Create 添加对象

通过这个例子也可以一并了解创建SessionFactory和使用session的方法:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class TestSessionFactory {
    public static void main(String[] args) {
        SessionFactory factory = new Configuration().configure("hibernate.cfg.xml").addAnnotatedClass(Student.class).buildSessionFactory();

        Session session = factory.getCurrentSession();

        try {
            //创建一个新的Student对象
            Student newStudent = new Student("Cony", "Li", "Conyli@gmail.com");

            //开始一个事务
            session.beginTransaction();

            //将Student对象保存进数据库
            session.save(newStudent);

            //提交事务
            session.getTransaction().commit();

        } finally {
            factory.close();
        }
    }
}

首先是创建SessionFactory的语句,如果configure()的参数是为空,Hibernate会自动寻找名称叫hibernate.cfg.xml的配置文件,但一般还是显式给出文件名较好。

.addAnnotatedClass(Student.class)这个方法用于添加被注解成Entity Class的类,其实就是将SessionFactory绑定到一个具体的表,之后的操作都是针对这个表。

创建好SessionFactory对象之后,使用.getCurrentSession()获取Session对象,之后就通过session对象操作数据库。

在使用完session对象后,要关闭。

在try语句中,创建一个新的Student对象,然后开始事务,写好执行代码后提交事务即可。这里如果添加上打印语句可以看到,在刚创建newStudent的时候,成员变量id被初始化在0,在写入数据库之后,id就有了值,对应数据库中的主键值。

Hibernate与主键

继续CRUD之前,先来看一下主键

主键已经都知道了,是用于区分一个表中的不同行,是一个int类型的数据,不能够是null。

一般主键定义了之后,都由数据库自行控制,每次表内有变化,这个值都会增加。但Hibernate也可以控制这个主键的一些策略,在刚才的Student对象中,可以加上一个注解:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private int id;

这个注解表示设置id生成的方式为IDENTITY,也就是交给数据库去生成。其他一些自动生成策略如下:

ID Generations Strategies
名称 解释
GenerationType.AUTO 针对不同的数据自动选取适合的策略
GenerationType.IDENTITY 使用数据库对于主键列的配置
GenerationType.SEQUENCE 根据数据库的sequence进行操作,比如Oracle数据库就有sequence这个概念
GenerationType.TABLE 使用一个数据表中的内容来确保唯一性生成id

还可以创建自己的生成ID的策略,需要继承org.hibernate.id.SequenceGenerator,然后重写generate(...)方法,需要注意的是,需要保证每次都生成一个独特的值。这里不再深入。

通过主键获取具体对象

使用session.get()方法来查询数据,查询的结果是一个Entity Class对象。

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class TestRetrieve {

    public static void main(String[] args) {
        SessionFactory factory = new Configuration().configure("hibernate.cfg.xml").addAnnotatedClass(Student.class).buildSessionFactory();

        Session session = factory.getCurrentSession();

        int primaryKey = 133;

        try {
            session.beginTransaction();
            Student student = session.get(Student.class, primaryKey);
            session.getTransaction().commit();
            if (student == null) {
                System.out.println("Not found");
            } else {
                System.out.println(student);
            }
        } finally {
            factory.close();
        }
    }
}

这里使用了session>get(Class, 主键)的获取方法,第一个参数是Entity Class的类文件,第二个参数是主键。

还需要注意的是,.get()方法也必须在一个事务里提交。

如果找不到数据,则返回一个null。

Retrieve 查询对象

刚才是通过主键获取对象,比较简单直接,但是只能用在知道主键的情况下。而更加一般的查询语句如下:

List<Student> students =session.createQuery("FROM Student s where s.lastName='Li'").getResultList();

.createQuery()方法中的参数非常类似于SQL的查询,只不过用Java中的类型代替了SQL中的表名,直接用取属性代替了列名。

这其中还可以使用类似SQL的OR语句或者like语句,编写一段代码试验一下:

try {
    session.beginTransaction();
    List<Student> students = session.createQuery("FROM Student s where s.lastName='Li'").getResultList();
    List<Student> students2 = session.createQuery("FROM Student s where s.lastName='Li' or s.firstName='Jenny'").getResultList();
    List<Student> students3 = session.createQuery("FROM Student s where s.email like '%sina.com'").getResultList();
    session.getTransaction().commit();
    for(Student s: students){
        System.out.println(s);
    }
    System.out.println("----------------------------------------------");
    for(Student s: students2){
        System.out.println(s);
    }
    System.out.println("----------------------------------------------");
    for(Student s: students3){
        System.out.println(s);
    }
}finally {
    factory.close();
}

注意,在Hibernate 5.2及更高版本中,需要使用.getResultList()方法,老的.list()已经被废弃。

Update 修改数据

修改数据有两种方法,一种是先获取一个具体对象,然后修改该对象的数据;一种是直接通过.createQuery()来执行类似SQL语句的更新。

第一种方法,Hibernate修改数据之后无需调用.save()方法,直接提交事务即可。例子如下:

public static void main(String[] args) {
    SessionFactory factory = new Configuration().configure("hibernate.cfg.xml").addAnnotatedClass(Student.class).buildSessionFactory();

    Session session = factory.getCurrentSession();

    int primaryKey = 3;

    try {
        session.beginTransaction();
        Student student = session.get(Student.class, primaryKey);
        System.out.println("修改前的数据是 " + student);

        student.setEmail("lee0709@vip.sina.com");
        student.setLastName("Lee");

        session.getTransaction().commit();

        System.out.println("修改后的数据是 " + student);
    } finally {
        factory.close();
    }
}

这种方式无需再调用save方法,直接提交事务就可以。另外一种方法:

try {
    session.beginTransaction();
    session.createQuery("update Student s set s.email='test@gmail.com' where s.id=2").executeUpdate();
    session.getTransaction().commit();
} finally {
    factory.close();
}

这种方法和查询的时候比较类似,采用的方式很像执行一个SQL语句,其中的属性名和列名都用对象属性来表示。.executeUpdate()返回一个int值,表示受影响的行数。

两种方法其实推荐第一种,因为比较像传统的面向对象的Java代码。

Delete 删除数据

删除数据和查询一样,也是有面向对象操作和提交Query操作两种方式,先来看面向对象操作:

int pk = 5;

try {
    session.beginTransaction();
    Student student = session.get(Student.class, pk);
    session.delete(student);
    session.getTransaction().commit();
} finally {
    factory.close();
}

面向对象操作也需要在事务中进行操作,.delete()的返回值是void。在提交完事务之后,数据库中已经没有了student对应的这一行,但是student的数据依然可以使用,因为这个对象还在内存中。

提交Query方式:

try {
    session.beginTransaction();
    session.createQuery("delete Student s where s.id=7").executeUpdate();
    session.getTransaction().commit();
}finally {
    factory.close();
}

同更新数据一样都执行.executeUpdate(),也会返回受影响的行数。

至此搞定了简单的增删改查。如果说Web开发的传统艺能之一是操作ORM进行增删改查,那么ORM的传统艺能就是操作约束关系了。