首页 > 代码库 > Hibernate、SpringMVC

Hibernate、SpringMVC

 

=====MVC等设计模式讲解=====

=====Hibernate=====

基本配置和操作

1)配置hibernate-cfg.xml

<hibernate-configuration>

    <session-factory>

    <!-- hibernate的方言,用来确定连接的数据库 -->

       <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

    <!-- 数据库的连接类 -->

       <property name="hibernate.connection.driver_class">

com.mysql.jdbc.Driver

</property>

    <!-- 数据库的连接字符串和用户名密码 -->

       <property name="hibernate.connection.url">

jdbc:mysql://localhost:3306/itat_hibernate

</property>

       <property name="hibernate.connection.username">root</property>

       <property name="hibernate.connection.password">

123456

</property>

    <!-- 在使用hibernate时会显示相应的SQL -->

       <property name="show_sql">true</property>

    <!-- 会自动完成类到数据表的转换 -->

       <property name="hibernate.hbm2ddl.auto">update</property>

    <!-- 加入实体类的映射文件 -->

       <mapping resource="org/zttc/itat/model/User.hbm.xml"/>

       <mapping resource="org/zttc/itat/model/Book.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

2)创建实体映射文件:

(略)

3)代码编写:创建SessionFatory

SessionFactory是线程安全的,所以SessionFatory要基于单利模式来创建。

    @Test

    public void test01() {

       Configuration cfg = new Configuration().configure();

       //cfg.buildSessionFactory();

//hibernate3中都是使用该种方法创建,但是在4中被禁用了,使用以下方法

       ServiceRegistry serviceRegistry =

new ServiceRegistryBuilder()

                         .applySettings(cfg.getProperties()).buildServiceRegistry();

       SessionFactory factory =

                       cfg.buildSessionFactory(serviceRegistry);

       Session session = null;

       try {

           session = factory.openSession();

           //开启事务

           session.beginTransaction();

           User u = new User();

           u.setNickname("张三");

           session.save(u);

           //提交事务

           session.getTransaction().commit();

       } catch (HibernateException e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           if(session!=null) session.close();

       }

    }

【总结】:

临时状态 à 持久状态:做save操作;

离线状态 à 临时状态:做delete操作;

有时候不知道对象是离线还是临时状态,这时候可以调用sessionsaveandupdate()方法

Hibernate的状态

l  Transient:是指临时状态,即new出一个对象,但是数据库中还没有这个对象;

l  Persistentnew一个对象并save后就是Persistent状态(持久化状态);

l  Detached:当session关闭并且对象保存在了数据库中,这时候就是离线状态。

关于Persistent

    @Test

    public void testTransient() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           session.beginTransaction();

           User u = new User();

           u.setBorn(sdf.parse("1976-2-3"));

           u.setUsername("zxl");

           u.setNickname("赵晓六");

           u.setPassword("123");

//以上u就是Transient(瞬时状态),表示未被session管理且数据库中没有

//save之后,被session所管理,且数据库中已经存在,此时就是Persistent状态

session.save(u); //u变成持久化状态,被sessio管理,正常保存

u.setNickname("赵晓其");//更新缓存内容,提交时候更新数据库

//如果没有提交那么上面的(从save开始)就是持久化状态,session中有完整的对象的//信息处于持久化状态的对象设置了新数据,提交的时候就会做更新操作

           session.getTransaction().commit();//导致更新操作执行

       } catch (Exception e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           HibernateUtil.close(session);

       }

    }

继续解释:

    @Test

    public void testPersistent02() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           session.beginTransaction();

           User u = new User();

           u.setBorn(sdf.parse("1976-2-3"));

           u.setUsername("zxq");

           u.setNickname("赵晓八");

           u.setPassword("123");

//save后缓存(session)保存这个u对象信息

           session.save(u);

           u.setPassword("222");//不会导致缓存中内容变化

           //缓存内容没有变化,所以不会执行(这部分有些不明白??)

           session.save(u);

           u.setNickname("赵晓吧");

           //下面语句不执行(持久化状态的update不执行,提交时候才会执行update

           session.update(u);

           u.setBorn(sdf.parse("1988-12-22"));

           //没有意义

           session.update(u);

            //执行update

           session.getTransaction().commit();

       } catch (Exception e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           HibernateUtil.close(session);

       }

    }

load出来的对象也是持久化对象:

    //此时uPersistent

    User u = (User)session.load(User.class, 10);

    @Test

    public void testDetach05() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           session.beginTransaction();

           User u = new User();

           //u.setId(110);

           u.setNickname("abc");

           //如果u是离线状态就执行update操作,如果是瞬时状态就执行Save操作

           //但是注意:该方法并不常用

           session.saveOrUpdate(u);

           session.getTransaction().commit();

       } catch (Exception e) {

           e.printStackTrace();

           if(session!=null) session.getTransaction().rollback();

       } finally {

           HibernateUtil.close(session);

       }

    }

懒加载

load()get()

当使用load加载一个对象的时候,如果取出的对象没有使用,那么就不会发sql——这既是Hibernate的“延迟加载(懒加载)”。且,该对象其实是一个代理对象,该对象中只有一个id的值,若打印该id也不会发sql(不用从数据库中取)。

    @Test

    public void testLazy02() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           User u = (User)session.load(User.class, 1);

           //此时一条sql都没有发,这就是hibernate的延迟加载

/**延迟加载指的就是,当完成load操作之后,并不会马山发出sql语句,只有在使用到该对象时才会发出sql,当完成load之后,u其实是一个代理对象,这个代理对象中仅仅只有一个id的值*/

           //此时不会发sql

           System.out.println(u.getId());

           //nickname没有值,必须去数据库中取。所以会发出sql

           System.out.println(u.getNickname());

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

【例】:

    @Test

    public void testLazy04() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           //get是只要一执行就会发出sqlget没有延迟加载

           User u = (User)session.get(User.class, 101);

//get的时候发现数据库中并没有该数据,所以unull,打印u.getId(),会抛出空指针异常,如果是使用load的话,会打印101(代理对象存储了id的值)

           System.out.println(u.getId());

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

【例】:

    @Test

    public void testLazy05() {

       Session session = null;

       try {

           session = HibernateUtil.openSession();

           User u = (User)session.load(User.class, 101);

           //由于id已经存在,所以不会抛出异常

           System.out.println(u.getId());

           //此时会去数据库中取数据,发现没有这个对象,但是u并不是空,所以会抛出ObjectNotFoundException

           System.out.println(u.getNickname());

       } catch (Exception e) {

           e.printStackTrace();

       } finally {

           HibernateUtil.close(session);

       }

    }

开发中的懒加载的问题

【情景演示】:

Dao中的load方法:

public User load(int id) {

    Session session = null;

    User u = null;

    try {

       session = HibernateUtil.openSession();

       u = (User)session.load(User.class, 1);

    } catch (Exception e) {

       e.printStackTrace();

    } finally {

       HibernateUtil.close(session);

    }

    return u; //这里返回的仅是一个代理对象,且此时session已经关闭

}

控制器中调用这个方法用来在页面显示:

@Test

public void testLazyQuestion() {

    UserDao ud = new UserDao();

    /*由于使用了loadload是有延迟加载的,返回的时候的u是一个代理对象,仅仅只有一个id

     * 但是在返回的时候Session已经被关闭了,此时当需要使用u的其他属性时就需要去数据库中

     * 但是Session关闭了,所以就抛出org.hibernate.LazyInitializationException no session

     */

    User u = ud.load(2);

    System.out.println(u.getUsername());

}

解决方法:

方法1:使用get方法;

方法2:在过滤器中获取session放到context中,然后在过滤器中关闭。Spring中使用openSessionInView

ID生成策略

<hibernate-mapping package="org.zttc.itat.model">

    <class name="Book" table="t_book">

        <id name="id">

            // assigned:表示要手动指定主键

            // uuid:表示自动生成一个uuid字符串,所以主键必须是String

            // native:也是自动生成,生成的是123等有序列,所以检索会快些

           // native的缺点是每次插入一条数据库都会查询一下数据库,开发中查询

           // 操作会比较多,所以开发中会使用native

            <generator class="uuid"/>

        </id>

        <property name="name"/>

        <property name="price"/>

        <property name="bookPage" column="book_page" type="int"/>

    </class>

</hibernate-mapping>

实体表映射-使用xml

单张表映射

【提示】:

l  实体字段名,比如bookName,映射到数据库最好映射成book_name

<property name="bookPage" column="book_page" type="int"/>

l  在实际开发中,一般不会使用Hibernate来生成表,一般的开发中,都是首先用PowerDesigner设计好数据库,然后生成SQL语句,然后用这个SQL语句来生成表。

这样的好处是,在开发数据库的时候可以为数据库创建索引和视图。

l  对效率要求不高的项目适合使用Hibernate

l  对效率比较高的项目,如果使用Hibernate的话,若想要效率也高,则可以这么做:增删改使用Hibernate,而查询使用原生态的SQL

l  Hibernate语句时,比如清楚一句Hibernate代码执行了几条SQL语句,否则性能可能大降!

关系映射

多对一(学生和班级)

l  关系一般是由“多”的一方来维护;

<!-- inverse=true表示不在自己这一端维护关系 -->

<set name="stus" lazy="extra" inverse="true">

    <key column="cid"/>

    <one-to-many class="Student"/>

</set>

l  添加的时候,一般都是先添加“一”的一方;

l  懒加载会多发一条SQL语句(抓取策略可以只发一条SQL语句……?)

l  关联(cascade:不要在“多”的一方使用关联。因为,比如,删除多的一方的一条记录,同时会删除“一”的一方对应的记录,但是如果“一”的一方的该条记录又关联了其他记录的话,就不能删除了。使用cascade的情况一般是:在“一”的一方删除时使用,特殊情况才会在add上做级联。

一对多(留言和评论)

        <property name="title"/>

        <property name="content"/>

<!-- 使用了lazy=extra之后会稍微智能一些,会根据去的值的不同来判断是调用count和获取投影 ,比如取出总条数一般情况下会取出所有数据的list然后计算listsize,但是用了extra后就会直接count(*)-->

        <set name="comments" lazy="extra">

        <!-- key用来指定在对方的外键的名称 -->

            <key column="mid"/>

        <!-- class用来设置列表中的对象类型 -->

            <one-to-many class="Comment"/>

        </set>

    </class>

双向关联(班级和学生)

(略)

实体表映射-使用注解

l  一个Hibernate项目是使用注解还是Annotation呢?

——小项目使用注解,大项目(一两百万行代码的)使用xml。因为大项目的类比较多,看xml文件更方便。大项目不用外键的方式????

多对一(学生和教室)

@Entity

@Table(name="t_classroom")

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

// mappedBy表示关系由对方的classroom属性维护,如果没有mappedBy的话,会生

// 成中间表

    @OneToMany(mappedBy="classroom")

    // 使用extrasql语句会较智能,比如计算条数会自动使用count(*)

    @LazyCollection(LazyCollectionOption.EXTRA)

    public Set<Student> getStus() {

       return stus;

    }

    public void setStus(Set<Student> stus) {

       this.stus = stus;

    }

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

}

@Entity

@Table(name="t_student")

public class Student {

    private int id;

    private String name;

    private String no;

    private Classroom classroom;

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    // fetch的默认值是EAGER,表示不使用懒加载,一条语句就能把相关的数据查询

    // 出来;如果值设置为LAZY的话,就会使用懒加载,会多发一条sql

    @ManyToOne(fetch=FetchType.LAZY)

    // 在自己这边生成的外键名是cid,哪一方维护关系就在哪一方加JoinColumn(就是

// 外键的意思)一般都由“多”的一方维护关系,即在自己这方配置对方的外键。

    @JoinColumn(name="cid") 

    public Classroom getClassroom() {

       return classroom;

    }

}

一对多(学生和教室)

(主要内容就是使用Extra的问题,查询属于教室的学生的数目)。

一对一(人和身份证号)

多对多

以上都不需要中间表,而这个“多对多”关系需要中间表。

【提示】:

多对多关系开发中一般不用,而是使用两个一对多关系来代替。

@Entity

@Table(name="t_admin")

public class Admin {

    private int id;

    private String name;

    private Set<Role> roles;

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    // 设置由对方维护关系

    @ManyToMany(mappedBy="admins")

    public Set<Role> getRoles() {

       return roles;

    }

}

@Entity

@Table(name="t_role")

public class Role {

    private int id;

    private String name;

    private Set<Admin> admins;

   

    public Role() {

       admins = new HashSet<Admin>();

    }

    public void add(Admin admin) {

       admins.add(admin);

    }

    @ManyToMany(mappedBy="admins")

    public Set<Role> getRoles() {

       return roles;

    }

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }  

    // joinColumns:用来设置自己在中间表的外键名;inverseJoinColumns用来设置

    // 对方在中间表的外键名

    @ManyToMany

    @JoinTable(name="t_role_admin",

joinColumns={@JoinColumn(name="rid")},

                      inverseJoinColumns={@JoinColumn(name="aid")})

    public Set<Admin> getAdmins() {

       return admins;

    }

    public void setAdmins(Set<Admin> admins) {

       this.admins = admins;

    }

}

专业-教室 -学生

基于Annotation

专业-教室:1对多

教室-学生:1对多

@Entity

@Table(name="t_stu")

//@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

public class Student {

    private int id;

    private String name;

    private String sex;

    private Classroom classroom;

    private int version;

   

    @Version

    public int getVersion() {

       return version;

    }

    public void setVersion(int version) {

       this.version = version;

    }

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    //LAZY就是XML中的select,EAGER就表示XML中的join

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="cid")

    public Classroom getClassroom() {

       return classroom;

    }

}

@Entity

@Table(name="t_classroom")

@BatchSize(size=20)

public class Classroom {

    private int id;

    private String name;

    private int grade;

    private Set<Student> stus;

    private Special special;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }  

    //由多的一方维护关系

    @OneToMany(mappedBy="classroom")

    @LazyCollection(LazyCollectionOption.EXTRA)

    @Fetch(FetchMode.SUBSELECT)

    public Set<Student> getStus() {

       return stus;

    }  

    @ManyToOne(fetch=FetchType.LAZY)

    @JoinColumn(name="spe_id")

    public Special getSpecial() {

       return special;

    }

}

@Entity

@Table(name="t_special")

public class Special {

    private int id;

    private String name;

    private String type;

    private Set<Classroom> clas;

   

    @Id

    @GeneratedValue

    public int getId() {

       return id;

    }

    @OneToMany(mappedBy="special")

    @LazyCollection(LazyCollectionOption.EXTRA)

    public Set<Classroom> getClas() {

       return clas;

    }

}

基于XML

略,重点提示:

(1)       设置<set>的时候,使用inverse,表示不在自己这边维护关系,如下配置classroom

<many-to-one name="special" column="spe_id" fetch="join"/>

<set name="stus" inverse="true" lazy="extra" fetch="subselect">

    <key column="cid"/>

    <one-to-many class="Student"/>

</set>

基于HQL的查询(重要)

/*基于?的条件的查询,特别注意:jdbc设置参数的最小下标是1hibernate0*/

List<Student> stus = session

.createQuery("from Student where name like ?")

                           .setParameter(0, "%%")

                                   .list();

/*还可以基于别名进行查询,使用:xxx来说明别名的名称,基于列表查询一定要用别名*/

List<Student> stus = session

.createQuery("from Student where name like :name and sex=:sex")

                  .setParameter("name", "%%")

                       .setParameter("sex", "")

                            .list();

常用HQL的查询(重要)

/*使用uniqueResult可以返回唯一的一个值注意返回值类型(uniqueResult的返回类型是Object*/

Long stus = (Long)session

.createQuery("select count(*)

 from Student where name like :name and sex=:sex")

                         .setParameter("name", "%%")

                            .setParameter("sex", "")

                                .uniqueResult();

/*基于投影的查询,通过在列表中存储一个对象的数组,注意返回值类型

基于投影的查询还可以基于DTO——DTO使用来传输数据*/

List<Object[]> stus = session

.createQuery("select stu.sex,

count(*) from Student stu group by stu.sex").list();

for(Object[] obj:stus) {

    System.out.println(obj[0]+":"+obj[1]);

}

【我的总结】:

group by的作用,select后的显示、计算是按照group by后进行的(group by后的记录相当于一张张字表,对这些字表进行select操作)。

/*如果对象中相应的导航对象,可以直接导航完成查询*/

List<Student> stus = session

.createQuery("select stu from Student stu

where stu.classroom.name=? and stu.name like ?")

                           .setParameter(0, "计算机教育班")

.setParameter(1, "%%")

.list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

/*可以使用in来设置基于列表的查询,此处的查询需要使用别名进行查询。

特别注意:使用in的查询必须在其他的查询之后*/

List<Student> stus = session

.createQuery("select stu from Student stu

where stu.name like ? and stu.classroom.id in (:clas)")

                  .setParameter(0, "%%")

.setParameterList("clas", new Integer[]{1,2})

                            .list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

【提示】:

基于列表查询要用别名:name

/*可以通过is null来查询为空的对象,sql一样不能使用=来查询null的对象*/

List<Student> stus = session

.createQuery("select stu from Student stu

where stu.classroom is null")

                            .setFirstResult(0)

.setMaxResults(15)

                                          .list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

/*使用对象的导航(内部使用Cross JOIN<笛卡尔积>)可以完成连接,但是是基于Cross JOIN,效率不高,可以直接使用JOINJoin = Inner Join)来完成连接*/

List<Student> stus = ession

.createQuery("select stu from Student stu

left join stu.classroom cla where cla.id=2")

                             .setFirstResult(0)

.setMaxResults(15).list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

HQL的连接查询(重要)

连接(Join)有三种:

(1)       left join

以下sqlt1left)中不符合条件(t1.id=t2.cid)的也打印出来,无值的字段为null

           select * from t_classroom t1

left join t_stu t2 on(t1.id=t2.cid)

(2)       right join:同理(1

(3)       inner join(即join):两边都有的才显示。

左、右连接常用语统计数据。

// 以下sql语句显示不正常,本意是想显示每个班级的学生人数,但是没有学生的班级也会显示人数:

select t1.name,count(*) from t_classroom t1

join t_stu t2

on(t1.id=t2.cid) group by t1.id

// 改成以下sql

select t1.name,count(t2.cid) from t_classroom t1

left join t_stu t2

on(t1.id=t2.cid) group by t1.id

l  查询每个班级的男生和女生的人数:

select t1.name,t2.sex,count(t2.cid)

from t_classroom t1

left join t_stu t2

on(t1.id=t2.cid)

group by t1.id,t2.sex

【提示】:

Hibernate的导航连接内部使用CROSS JOIN(全连接),效率很低!

l  查询2班的所有学生:

/*使用对象的导航可以完成连接,但是是基于Cross JOIN,效率不高,可以直接使用JOIN来完成连接*/

List<Student> stus = session

                  .createQuery("select stu from Student stu

left join stu.classroom cla where cla.id=2").setFirstResult(0)

.setMaxResults(15).list();

for(Student stu:stus) {

    System.out.println(stu.getName());

}

l  查询每个班的人数

/*使用对象的导航可以完成连接,但是是基于Cross JOIN,效率不高,可以直接使用JOIN来完成连接

思考:如果把classroom放前面呢?如果使用count(*)会如何?*/

List<Object[]> stus = session

                  .createQuery("select

cla.name,count(stu.classroom.id)

from Student stu

right join stu.classroom cla

group by cla.id").list();

for(Object[] stu:stus) {

System.out.println(stu[0]+","+stu[1]);

}

l  查询学生的信息、包括所在班级(班级名)、专业名

SQL_1

List<Ojbet[]> stus =

session.createQuery(“select stu.id,stu.name,stu.sex,cla.name,spe.name

from Student stu

left join stu.classroom cla

left join cla.special spe”).list();

for( Object[] obj:stus ){

    ……

}

之前查询的字段,然后获取信息的时候,都是用Object[]接收返回值然后通过Object的索引获取索引值的但是实际开发中如果这样做就很不方便(如SQL_1,因为一一的取出索引值。这时候就可以使用DTO了,专门用来传输数据,没有任何存储意义。

>>>使用DTO解决以上问题:

新建一个DTO对象,要从数据库查询的哪些字段名就添加哪些属性名:

public class StudentDto {

    private int sid;

    private String sname;

    private String sex;

    private String cname;

    private String spename;

    get()…;

    set()…;

}

HQL查询:

/*直接可以使用new XXObject完成查询,注意,一定要加上Object的完整包名这里使用的new XX,必须在对象中加入相应的构造函数*/

List<StudentDto> stus = session

                  .createQuery("select

new org.zttc.itat.model.StudentDto(

stu.id as sid,stu.name as sname,stu.sex as

sex,cla.name as cname,spe.name as spename)

                    from Student stu

left join stu.classroom cla

left join cla.special spe").list();

for(StudentDto stu:stus) {        

System.out.println(stu.getSid()

+","+stu.getSname()+","+stu.getSex()+","

+stu.getCname()+","+stu.getSpename());

}

【提示】:

Criteria基本上在开发中不用!(我们项目组竟然用了……)

l  统计每个专业的学生的人数:

List<Object[]> stus = session.createQuery("

select spe.name,count(stu.classroom.special.id)

from Student stu

right join

stu.classroom.special spe

group by spe");

l  统计人数大于150的专业:

/*having是为group来设置条件的*/

List<Object[]> stus = session.createQuery("

select spe.name,

(count(stu.classroom.special.id))

from Student stu

right join

stu.classroom.special spe

group by spe

having count(stu.classroom.special.id)>150").list();

for(Object[] obj:stus) {

    System.out.println(obj[0]+":"+obj[1]);

}

l  统计每个专业的男女生人数:

/*having是为group来设置条件的*/

List<Object[]> stus = session.createQuery("

select stu.sex,spe.name

(count(stu.classroom.special.id))

from Student stu

right join

stu.classroom.special spe

group by spe,stu.sex").list();

for(Object[] obj:stus) {

    System.out.println(obj[0]+":"+obj[1]+","+obj[2]);

}

抓取策略

使用映射文件的情况下:

/*

(1)默认情况会发出3SQL语句,一条取student,一条取Classroom,一条取Special

(3)通过设置XML中的<many-to-one name="classroom" column="cid" fetch="join"/>可以完成对抓取的设置

(4)如果使用Annotation的话,Hibernate会自动使用Join查询,只发出一条SQL

使用Annotation默认就是基于join抓取的,所以只会发出一条sql*/

session = HibernateUtil.openSession();

Student stu = (Student)session.load(Student.class, 1);  System.out.println(stu.getName()+","+stu.getClassroom().getName()+","+stu.getClassroom().getSpecial().getName());

如果不想发出3SQL,可以在映射文件中这么配置,使用fetch=”join”(默认值是select

<class name="Student" table="t_stu">

    <!-- <cache usage="read-only"/> -->

    <id name="id">

        <generator class="native"/>

    </id>

     <version name="version"/>

    <property name="name"/>

    <property name="sex"/>

    <many-to-one name="classroom" column="cid" fetch="join"/>

</class>

专业是通过classroom来获取的,所以,同时要在classroom中配置fetch=”join”

<class name="Classroom" table="t_classroom">

    <id name="id">

        <generator class="native"/>

    </id>

    <property name="name"/>

    <property name="grade"/>

    <many-to-one name="special" column="spe_id" fetch="join"/>

    <set name="stus" inverse="true" lazy="extra" fetch="subselect">

        <key column="cid"/>

        <one-to-many class="Student"/>

    </set>

</class>

【警告】:

以上使用fetch=”join”会有这样的问题,就是,即使我query中不查询班级和专业,发出的SQL也会去查专业和学生!也就是说,使用了fetch=”join”后,延迟加载就不生效了!

/*使用fetch=join虽然可以将关联对象抓取,但是如果不使用关联对象也会一并查询出来这样会占用相应的内存*/

session = HibernateUtil.openSession();

Student stu = (Student)session.load(Student.class, 1);

//使用fetch=”join”,延迟加载就失效

System.out.println(stu.getName());

【对于Annotation的问题】:

因为在使用Annotation的情况下,默认就是基于join的抓取策略,所以,比如,我们只查学生的话,同时会把班级、专业信息都查出来,即把关联的信息都查出来。

=====Spring=====

=====SpringMVC=====

简单的HelloWorld示例:

如果往数据库中存入中文字符的话,则要处理中文乱码的问题,方法是:

web.xml中做以下配置:

JSR是记录Java标准的更新。

输入验证:

在实体里使用注解限定属性:

public class User {

    private String username;

    private String password;

    private String nickname;

    private String email;

    // 这里加上注解:

    @NotEmpty(message="用户名不能为空")

    public String getUsername() {

       return username;

    }

    @Size(min=1,max=10,message="密码的长度应该在110之间")

    public String getPassword() {

       return password;

    }

}

controller中使用@Validate来验证用户的输入,BindingResult返回验证结果(实体中定义的)

    @RequestMapping(value=http://www.mamicode.com/"/{username}/update",

method=RequestMethod.POST)

   // BindingResult用来存放错误信息(写在实体里的)

// BindingResult要紧跟@Validate后面

    public String update(@PathVariable String username,

@Validated User user,BindingResult br) {

       if(br.hasErrors()) {// 有错误信息

           //如果有错误直接跳转到add视图

           return "user/update";

       }

       users.put(username, user);

       return "redirect:/user/users";

    }

    //在具体添加用户时,是post请求,就访问以下代码

    @RequestMapping(value=http://www.mamicode.com/"/add",method=RequestMethod.POST)

    public String add(@Validated User user,BindingResult br,

@RequestParam("attachs")MultipartFile[] attachs,

HttpServletRequest req) throws IOException {

//一定要紧跟Validate之后写验证结果类

       if(br.hasErrors()) {

           //如果有错误直接跳转到add视图

           return "user/add";

       }

       String realpath = req.getSession().getServletContext().

getRealPath("/resources/upload");

       System.out.println(realpath);

       for(MultipartFile attach:attachs) {

           if(attach.isEmpty()) continue;

           File f = new File(realpath+"/"+attach

.getOriginalFilename());

           FileUtils.copyInputStreamToFile(attach

.getInputStream(),f);

       }

       users.put(user.getUsername(), user);

       return "redirect:/user/users";

    }

然后在jsp页面显示这些错误信息:

<!-- 此时没有写action,直接提交会提交给/add -->

// 这里为什么是直接给/add不明白,可能是modelAttribute=”user”而使用了参数// 匹配吧。

<sf:form method="post" modelAttribute="user"

                                   enctype="multipart/form-data">

// path=”usernama”就相当于是:name=”username”,会自动使用user.Set方法

    Username:<sf:input path="username"/>

<sf:errors path="username"/><br/>

    Password:<sf:password path="password"/>

<sf:errors path="password"/><br/>

    Nickname:<sf:input path="nickname"/><br/>

    Email:<sf:input path="email"/>

<sf:errors path="email"/><br/>

    Attach:<input type="file" name="attachs"/><br/> 

<input type="file" name="attachs"/><br/> 

<input type="file" name="attachs"/><br/> 

     <input type="submit" value="添加用户"/>

</sf:form>

显示用户:

// value=http://www.mamicode.com/”/{username}” 表示username是一个路径参数(@PathVariable

// ?这里的这个username好像是跟show方法中的参数保持一致的……

@RequestMapping(value=http://www.mamicode.com/"/{username}",method=RequestMethod.GET)

public String show(@PathVariable String username,Model model) {

    // jsp页面就能用${user.username}取值了

    model.addAttribute(users.get(username));

    return "user/show";

}

【提示】:

l  关于请求方法:只要不是“更新”操作,就用GET请求。

l  关于model.addAttribute(),如果只写参数,那么其key则是使用对象的类型比如:

model.addAttribute(new User());等价于:model.addAttribute(“user”,new User());

修改用户:

// JSP页面

<sf:form method="post" modelAttribute="user">

    Username:<sf:input path="username"/>

<sf:errors path="username"/><br/>

    Password:<sf:password path="password"/>

<sf:errors path="password"/><br/>

    Nickname:<sf:input path="nickname"/><br/>

    Email:<sf:input path="email"/>

<sf:errors path="email"/><br/>

    <input type="submit" value="修改用户"/>

</sf:form>

    // 页面跳转

@RequestMapping(value=http://www.mamicode.com/"/{username}/update",

method=RequestMethod.GET)

    public String update(@PathVariable String username,Model model) {

       model.addAttribute(users.get(username)); // 回显

       return "user/update";// 到此页面

    }

// 真正的修改

@RequestMapping(value=http://www.mamicode.com/"/{username}/update",

method=RequestMethod.POST)

public String update(@PathVariable String username,

@Validated User user,BindingResult br) {

    if(br.hasErrors()) {

       //如果有错误直接跳转到add视图

       return "user/update";

    }

    users.put(username, user);

    return "redirect:/user/users";

}

删除用户:

<c:forEach items="${users }" var="um">

    ${um.value.username }

    ----<a href="${um.value.username }">${um.value.nickname }</a>

    ----${um.value.password }

    ----${um.value.email }—

<a href="${um.value.username }/update">修改</a>

    <a href="${um.value.username }/delete">删除</a><br/>

</c:forEach>

    @RequestMapping(value=http://www.mamicode.com/"/{username}/delete",

method=RequestMethod.GET)

    public String delete(@PathVariable String username) {

       users.remove(username);

       return "redirect:/user/users";

    }

用户登陆、异常处理:

局部异常处理

局部异常处理是放在一个单独的Controller中的异常处理,只为这一个Controller服务。

步骤

1)自定义异常:

public class UserException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public UserException() {

       super();

    }

    public UserException(String message, Throwable cause) {

       super(message, cause);

    }

    public UserException(String message) {

       super(message);

    }

    public UserException(Throwable cause) {

       super(cause);

    }

}

2)在Controller中使用异常:

   

    @RequestMapping(value=http://www.mamicode.com/"/login",method=RequestMethod.POST)

    public String login(String username,String password,

HttpSession session) {

       if(!users.containsKey(username)) {

           throw new UserException("用户名不存在");//在页面可以打印

       }

       User u = users.get(username);

       if(!u.getPassword().equals(password)) {

           throw new UserException("用户密码不正确");

       }

       session.setAttribute("loginUser", u);

       return "redirect:/user/users";

    }

   

    /**

     * 局部异常处理,仅仅只能处理这个控制器中的异常

     */

    @ExceptionHandler(value=http://www.mamicode.com/{UserException.class})

    public String handlerException(UserException e,

HttpServletRequest req) {

       req.setAttribute("e",e);

       return "error";// 返回到error页面

    }

3)在error.jsp中显示异常信息

发现错误:

<h1>${e.message}</h1>

全局异常处理

使用方法是在springMVC配置文件中将自己定义的异常配置成全局的。

    <bean id="exceptionResolver" class="org.springframework.web.servlet.

handler.SimpleMappingExceptionResolver">

       <property name="exceptionMappings">

           <props>

              // 如果发现UserException就跳转到error页面,异常会放在

//exception中,所以在jsp页面要使用exception来取出异常信息

              <prop key="zttc.itat.model.UserException">error</prop>

              // 如果发现空指针异常就到error2页面

<prop key="java.lang.NullPointException">error2</prop>

           </props>

       </property>

    </bean>

在页面打印异常信息:

发现错误:

<h1>${exception.message}</h1>

关于springMVC中的静态文件

比如,我们在页面中引用了一个css文件:

</title>

<link rel="stylesheet" href="<%=request.getContextPath()%>/resources/css/main.css" type="text/css">

</head>

然后我们试图获取这个文件,比如在浏览器中输入文件地址(或者访问使用了该样式的jsp文件,样式不起作用):

http://localhost:8080/项目名/css/main.css

这时候会报404错误。

处理方法是,将所有这些静态文件放到某个文件夹中,然后在spring配置文件中配置,比如,将main.css文件放到/resources/css/目录下,然后在spring配置文件中做如下配置:

<!-- 将静态文件指定到某个特殊的文件夹中统一处理 -->

// location是指要处理的目录;mapping是指要处理的文件,两个星的第一个星代表

// 当前文件夹(resources)的内容第二个星表示文件夹的子文件夹的内容

// 注意要加的斜杠

<mvc:resources location="/resources/" mapping="/resources/**"/>

<bean name="/welcome.html" class="zttc.itat.controller.WelcomeController"></bean>

文件上传:

文件上传现在一般都用Apache的上传包:

commons-fileupload-1.2.2-bin

commons-io-2.1

jsp页面:

<sf:form method="post" modelAttribute="user"

                                   enctype="multipart/form-data">

// path=”usernama”就相当于是:name=”username”,会自动使用user.Set方法

    Username:<sf:input path="username"/>

<sf:errors path="username"/><br/>

    Password:<sf:password path="password"/>

<sf:errors path="password"/><br/>

    Nickname:<sf:input path="nickname"/><br/>

    Email:<sf:input path="email"/>

<sf:errors path="email"/><br/>

    Attach:<input type="file" name="attachs"/><br/> 

<input type="file" name="attachs"/><br/> 

<input type="file" name="attachs"/><br/> 

     <input type="submit" value="添加用户"/>

</sf:form>

如果想上传文件就必须在spring配置文件中配置MultipartResolver视图:

    <!-- 设置multipartResolver才能完成文件上传 -->

    <bean id="multipartResolver"class="org.springframework.

web.multipart.commons.CommonsMultipartResolver">

       <property name="maxUploadSize" value="5000000"></property>

    </bean>

Controller

    //注意:我们上传一个文件的时候参数只要写MultipartFile attachs

    //这样上传的文件自动和attachs做匹配;

    //但是如果是上传多个文件就要用数组MultipartFile[]attachs,这时候文件

    //就不能自动匹配了,解决方法是下面的写法:

@RequestMapping(value=http://www.mamicode.com/"/add",method=RequestMethod.POST)

    public String add(@Validated User user,BindingResult br,

@RequestParam("attachs")MultipartFile[] attachs,

HttpServletRequest req) throws IOException {

//一定要紧跟Validate之后写验证结果类

       if(br.hasErrors()) {

           //如果有错误直接跳转到add视图

           return "user/add";

       }

       String realpath = req.getSession().getServletContext().

getRealPath("/resources/upload");

       System.out.println(realpath);

       for(MultipartFile attach:attachs) {

           //如果多个输入框有的没输入,即有空文件的情况

           if(attach.isEmpty()) continue;

           File f = new File(realpath+"/"+attach

.getOriginalFilename());

           FileUtils.copyInputStreamToFile(attach

.getInputStream(),f);

       }

       users.put(user.getUsername(), user);

       return "redirect:/user/users";

    }

Controller中返回JSON

@RequestMapping(value=http://www.mamicode.com/"/{username}",method=RequestMethod.GET)

public String show(@PathVariable String username,Model model) {

    model.addAttribute(users.get(username));

    return "user/show";

}

//1)若要控制返回某个值的话,则要使用@ReponseBody

//2params=”par”的意思是,若要访问show1,那么必须要有一个参数为par

// 如果没有参数就访问上面的这个show

@RequestMapping(value=http://www.mamicode.com/"/{username}",

method=RequestMethod.GET,params="par")

@ResponseBody

public User show(@PathVariable String username) {

    return users.get(username);

}

【说明】:

访问第一个show的方式:http://localhost:8080/项目名/user/jack/

访问第二个show的方式:http://localhost:8080/项目名/user/jack?par

(这样直接访问的话,会报406错误,缺少http头)

如果想返回一个json,首先导入转json的开发包:jackson-all-1.9.4.jar

然后再访问第二个showhttp://localhost:8080/项目名/user/jack?par

这样就直接将user转成json格式显示。

Hibernate、SpringMVC