首页 > 代码库 > 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操作;
有时候不知道对象是离线还是临时状态,这时候可以调用session的saveandupdate()方法
Hibernate的状态
l Transient:是指临时状态,即new出一个对象,但是数据库中还没有这个对象;
l Persistent:new一个对象并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出来的对象也是持久化对象:
//此时u是Persistent
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是只要一执行就会发出sql,get没有延迟加载
User u = (User)session.get(User.class, 101);
//get的时候发现数据库中并没有该数据,所以u是null,打印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();
/*由于使用了load,load是有延迟加载的,返回的时候的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:也是自动生成,生成的是1、2、3等有序列,所以检索会快些
// 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然后计算list的size,但是用了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")
// 使用extra后sql语句会较智能,比如计算条数会自动使用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设置参数的最小下标是1,hibernate是0*/
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,效率不高,可以直接使用JOIN(Join = 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:
以下sql:t1(left)中不符合条件(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)默认情况会发出3条SQL语句,一条取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());
如果不想发出3条SQL,可以在映射文件中这么配置,使用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="密码的长度应该在1和10之间")
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
//(2)params=”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
然后再访问第二个show:http://localhost:8080/项目名/user/jack?par
这样就直接将user转成json格式显示。
Hibernate、SpringMVC