完整的MVC框架(前端、后台和数据库)

终于学完了数据库的连接,可以做一个完整的项目了,以前做的练习都没有关联到数据库,没法进行事务。

 

MVC框架

先上图:

画的图,有点乱,但是大概意思还是可以理解。

 

 

这个练习是简单的存储一个学生读了哪些书,存进数据库中,当然也可以查询。

主页图:

代码奉上:

index.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    <title>学生图书信息管理</title>
  </head>
  
  <body>
    <br><a href='<c:url value="/StudServlet?cmd=query"></c:url>' >学生图书信息查询</a>
    <br/><br/>
    <hr/>
    <form action='<c:url value="/StudServlet?cmd=save"></c:url>' method="post">
    	姓名<input type="text" name="name" /><br/><br/>
    <fieldset style="width: 200px">
    	<legend>图书1</legend>
    	书名<input type="text" name="bookName" />
    	价格<input type="text" name="price" />
    </fieldset>
    <fieldset style="width: 200px">
    	<legend>图书2</legend>
    	书名<input type="text" name="bookName" />
    	价格<input type="text" name="price" />
    </fieldset>
    <input type="submit" value="保存">
    </form>
    <br/>
    <a href='<c:url value="StudServlet?cmd=abc"></c:url>' >只是随便转转,不要开启事务</a>
  </body>
</html>

 


index请求的servlet.java

package cn.hncu.stud.servlet;

import java.io.IOException;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.hncu.domain.Book;
import cn.hncu.domain.Stud;
import cn.hncu.stud.service.IStudService;
import cn.hncu.stud.service.IStudServiceImpl;
import cn.hncu.utils.TxProxy;

public class StudServlet extends HttpServlet {
//	IStudService service=new IStudServiceImpl();//这是直接在service层中开启事务,也是version 1
//	IStudService service=TxProxy.getProxy(IStudServiceImpl.class);//version 2
	IStudService service=TxProxy.getProxy(new IStudServiceImpl());//version 3
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doPost(request, response);
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			request.setCharacterEncoding("utf-8");
			String cmd=request.getParameter("cmd");
			if("query".equals(cmd)){//不直接用cmd去判断,是为了防止cmd为null,更安全些
				query(request, response);
			}else if("save".equals(cmd)){
				save(request, response);
			}else if("abc".equals(cmd)){
				abc(request,response);
			}
	}

	private void query(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		List<Map<String,String>> stud=service.query();
		request.setAttribute("stud", stud);
		request.getRequestDispatcher("/jsps/show.jsp").forward(request, response);
	}
	private void save(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		//1.收集   2.组织
		String name=request.getParameter("name");
		//图书信息
		String[] bookNames=request.getParameterValues("bookName");
		String prices[]=request.getParameterValues("price");
		
		Stud stud=new Stud();
		stud.setName(name);
		if(bookNames==null||bookNames.length==0){//防护一下,防止为空,下面的price也应该做防护的,我省略了
			return;
		}
		for(int i=0;i<bookNames.length;i++){
			Book book=new Book();
			book.setName(bookNames[i]);
			book.setPrice(Double.parseDouble(prices[i]));

			//在这里完成两个值对象的“一对多”关系的数据封装
			stud.getBook().add(book);
			book.setStud(stud);
		}
		
		//3 调用service层
		try {
			service.save(stud);//把stud放进去就可以
			System.out.println("SUCCESSFULL");
		} catch (Exception e) {
			//导向失败页面
		}
		
	}
	private void abc(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			service.abc();
	}
}

 

servlet要组织和封装从前台页面发来的数据,这里用了两个值对象:

Stud.java

package cn.hncu.domain;

import java.util.ArrayList;
import java.util.List;

/*
 * 一对多中“一方”值对象的建法
 */
public class Stud {
	private String id;
	private String name;
	
	//为多方开一个集合,用来体现多表中“一对多”的关系
	private List<Book> books=new ArrayList<Book>();//注意:该集合要在类构造或之前就new出来

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public List<Book> getBook() {
		return books;
	}

	public void setBook(List<Book> book) {
		this.books = book;
	}

	@Override
	public String toString() {
		return "Stud [id=" + id + ", name=" + name + ", book=" + books + "]";
	}
	
}

 

Book.java

package cn.hncu.domain;
/*
 * 一对多中的“多”方值对象的建法
 */
public class Book {
	private Integer id;//基本数据类型全用包装类来声明,为以后使用框架做技术准备----包装类能够兼容框架(因为一般框架都会使用类反射)
	private String name;
	private Double price;
	
	//专为“一”方添加一个对象类型的变量(不直接使用String studid)-----体现多表中的“一对多”关系
	private Stud stud=new Stud();//设置书的主人

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Double getPrice() {
		return price;
	}

	public void setPrice(Double price) {
		this.price = price;
	}

	public Stud getStud() {
		return stud;
	}

	public void setStud(Stud stud) {
		this.stud = stud;
	}
	
	//多表关联时toString()有一个陷阱:不要出现一方输出另外一方,另外一方又输出这一方的情况,形成无穷循环
	@Override
	public String toString() {
		return "Book [id=" + id + ", name=" + name + ", price=" + price + "]";
	}
	
	
}



 

servlet调用service,service被动态代理的TxProxy.java

package cn.hncu.utils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

public class TxProxy implements InvocationHandler {
	private Object srcObj=null;
	private TxProxy(Object srcObj) {
		this.srcObj=srcObj;
	}
	/*version 1
	public static Object getProxy(Object srcObj){
		Object newObj=Proxy.newProxyInstance(
				TxProxy.class.getClassLoader(), 
				srcObj.getClass().getInterfaces(), 
				new TxProxy(srcObj));
		return newObj;
	}
	*/
	/*version 2
	public static<T> T getProxy(Class<T> c){
		Object obj=null;
		try {
			obj = c.newInstance();
		} catch (Exception e) {
			throw new RuntimeException(c+"没有空参方法!");
		}
		Object newObj=Proxy.newProxyInstance(
				TxProxy.class.getClassLoader(), 
				obj.getClass().getInterfaces(), 
				new TxProxy(obj));
		return (T)newObj;
	}
	*/
	public static<T> T getProxy(T srcObj){
		Object newObj=Proxy.newProxyInstance(
				TxProxy.class.getClassLoader(), 
				srcObj.getClass().getInterfaces(), 
				new TxProxy(srcObj));
		return (T)newObj;
	}
	/*version 1与version 2公用
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Connection con=null;
		Object returnObj=null;
		
		try {
			con=ConnUtils5.getConn();
			con.setAutoCommit(false);
			System.out.println("开启事务.........");
			
			returnObj=method.invoke(srcObj, args);//放行
			
			con.commit();//
			System.out.println("提交事务.......");
		} catch (Exception e) {
			con.rollback();
			System.out.println("回滚事务.........");
		}finally{
			try {
				con.setAutoCommit(true);
				con.close();
			} catch (Exception e) {
				throw new RuntimeException(e.getMessage(), e);
			}
		}
		
		return returnObj;
	}
	*/
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		if(method.isAnnotationPresent(Transaction.class)){
			System.out.println("此方法存在注解,进行拦截......");
			Connection con=null;
			Object returnObj=null;
			
			try {
				con=ConnUtils5.getConn();
				con.setAutoCommit(false);
				System.out.println("开启事务.........");
				
				returnObj=method.invoke(srcObj, args);//放行
				
				con.commit();//
				System.out.println("提交事务.......");
			} catch (Exception e) {
				con.rollback();
				System.out.println("回滚事务.........");
			}finally{
				try {
					con.setAutoCommit(true);
					con.close();
				} catch (Exception e) {
					throw new RuntimeException(e.getMessage(), e);
				}
			}
			
			return returnObj;
		}
		System.out.println("没有注解,直接放行....");
		return method.invoke(srcObj, args);
	}
	
}


service层的接口IStudService.java

package cn.hncu.stud.service;

import java.util.List;
import java.util.Map;

import cn.hncu.domain.Stud;
import cn.hncu.utils.Transaction;

public interface IStudService {
	public abstract List<Map<String,String>> query();
	
	@Transaction
	public abstract void save(Stud stud) throws Exception ;

	public abstract void abc();
}

 

接口用到的注解:

Transaction.java

package cn.hncu.utils;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(value=ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transaction {
}


 


实现类IStudServiceImpl.java

package cn.hncu.stud.service;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import cn.hncu.domain.Stud;
import cn.hncu.stud.dao.BookDAO;
import cn.hncu.stud.dao.BookJdbcDao;
import cn.hncu.stud.dao.StudDAO;
import cn.hncu.stud.dao.StudJdbcDao;
import cn.hncu.utils.ConnUtils5;
/*
 * 我们以后开发时通常要采用一个dao独立操作一个表,系统中有几个实体表就写几个dao,
 * 框架也是这么做的,架构好。
 * 
 * 采用事务的场合:
 * 1.如果只有一个dao,但要执行多条sql语句且涉及增、删、改操作时要开启事务
 * 2.如果一个service调用多个dao,通常也要开启事务
 */
public class IStudServiceImpl implements IStudService{
	
	public IStudServiceImpl() {
	}
	StudDAO stuDao=new StudJdbcDao();
	BookDAO booDao=new BookJdbcDao();
	@Override
	public List<Map<String, String>> query() {
		return stuDao.query();
	}
	/*
	 * 事务要在service层做,dao层抛异常,在service抓异常,然后就行事务回滚,或者没有异常,提交
	 * 但是,service拿到的要和dao拿到的是同一个Connection对象,否则无法进行提交或者回滚
	 */
	@Override
	public void save(Stud stud) throws Exception {
		stuDao.save(stud);
	    booDao.save(stud.getBook());
	}
	@Override
	public void abc() {
		System.out.println("什么都不做,只是来转转.....");
		
	}
	
}


service调用dao层,由于要一个表要对应一个dao,这里有stud表和book表,所以有两个dao

接口StudDAO.java

package cn.hncu.stud.dao;

import java.util.List;
import java.util.Map;

import cn.hncu.domain.Stud;

public interface StudDAO {
	public abstract List<Map<String,String>> query();

	public abstract void save(Stud stud) throws Exception;
}


实现类StudJdbcDao.java

package cn.hncu.stud.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import cn.hncu.domain.Stud;
import cn.hncu.utils.ConnUtils5;

public class StudJdbcDao implements StudDAO{

	@Override
	public List<Map<String, String>> query() {
		List<Map<String, String>> list=new ArrayList<Map<String,String>>();
		//一个map就是一行数据,List<Map<>>就是整个数据表
		Connection con=ConnUtils5.getConn();
		try {
			ResultSet rs=con.createStatement().executeQuery("select * from stud");
			while(rs.next()){
				Map<String, String> map=new HashMap<String, String>();
				map.put("id", rs.getString("id"));
				map.put("name", rs.getString("name"));
				list.add(map);
			}
			rs.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			try {
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		return list;
	}


	@Override
	public void save(Stud stud) throws Exception {
		Connection con=ConnUtils5.getConn();
		String id=UUID.randomUUID().toString().replace("-", "");
		String sql="insert into stud(id,name) values(?,?)";
		PreparedStatement pst=con.prepareStatement(sql);
		pst.setString(1, id);
		pst.setString(2, stud.getName());
		pst.executeUpdate();
//		int a=pst.executeUpdate();
		//System.out.println("影响 "+a+" 行");
		stud.setId(id);//为了“多方能够拿到一方的id,在这里补一方的id”
	}

}


接口BookDAO.java

package cn.hncu.stud.dao;

import java.util.List;

import cn.hncu.domain.Book;

public interface BookDAO {
	public abstract void save(List<Book> list) throws Exception;
}


实现类BookJdbcDao.java

package cn.hncu.stud.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

import cn.hncu.domain.Book;
import cn.hncu.utils.ConnUtils5;

public class BookJdbcDao implements BookDAO{


	@Override
	public void save(List<Book> list) throws Exception {
		Connection con=ConnUtils5.getConn();
		String sql="insert into book(name,price,studid) values(?,?,?)";
		//          INSERT INTO book(NAME,price,studid) VALUES("aa",33.5,"1");
		PreparedStatement pst=con.prepareStatement(sql);
		for(Book book:list){
			pst.setString(1, book.getName());
			pst.setDouble(2, book.getPrice());
			pst.setString(3, book.getStud().getId());
			pst.addBatch();//此处不要参数!!!!!添加到批处理(有很多条sql语句要执行时,用批处理)
		}
		pst.executeBatch();//执行批处理
	
	}
	
}

 

类和包:


效果图:

存储到数据库:

学生表:

图书表:

查询:

 

在项目中又有两个新的知识点:

ThreadLocal类:

java.lang.ThreadLocal<T>

这个类本质上就是里面维护着一个HashMap<Thread,Object>,key为线程,get方法就是通过Thread.currentThread()判断当前线程,然后在map中找到对应的Object,然后返回出去。

即:同一个线程拿同一个对象,不同线程则是不同对象。

 

这里做了一些练习:

ThreadLocalUtil.java

package cn.hncu.threadLocDemo;

import java.util.Random;
public class ThreadLocalUtil {
//	private static ThreadLocal<Object> t1=new ThreadLocal<Object>();//线程管理池Map<Thread,Object>
	private static MyThreadLocal<Object> t1=new MyThreadLocal<Object>();//线程管理池Map<Thread,Object>
	
	public static Object getObj(){
		Object obj=t1.get();//从池中拿一个obj
		if(obj==null){//如果池中没有
			Random r=new Random();
			obj=r.nextInt(500);
			t1.set(obj);//则new obj放进池中去
		}
		return obj;
	}
}

 

测试类Client.java

package cn.hncu.threadLocDemo;

import org.junit.Test;

public class Client {
	
	@Test
	public void test1(){
		Object o1=ThreadLocalUtil.getObj();
		Object o2=ThreadLocalUtil.getObj();
		System.out.println("o1: "+o1+",o2: "+o2);
		System.out.println("o1==o2?  "+(o1==o2));
	}
	@Test
	public void test2(){
		test1();
		System.out.println();
		Object o3=ThreadLocalUtil.getObj();
		Object o4=ThreadLocalUtil.getObj();
		System.out.println("o3: "+o3+",o4: "+o4);
		System.out.println("o3==o4?  "+(o3==o4));
		System.out.println();
		new Thread(){
			@Override
			public void run() {
				Object o4=ThreadLocalUtil.getObj();
				Object o5=ThreadLocalUtil.getObj();
				System.out.println("o4: "+o4+",o5: "+o5);
				System.out.println("o4==o5?  "+(o4==o5));
				System.out.println();
			}
		}.start();
	}
	/*
	 * 测试结果:obj1,obj2,obj3,obj4由于是同一个线程,所以obj也都相同,
	 * obj5和obj6两者相同,但是他们是另外一个线程获取的,所以和obj1`obj4是不相等的
	 */
}


测试图:

在知道ThreadLocal的功能后,我们自己模拟了一下它的功能:

MyThreadLocal.java

package cn.hncu.threadLocDemo;

import java.util.HashMap;
import java.util.Map;
/*
 * 自己模拟ThreadLocal功能,便于理解
 * 虽然表面的功能能够模拟出来,但是ThreadLocal真正的功能远远比这强,比如内存管理
 */
public class MyThreadLocal<Object> {
	private Map<Thread, Object> map=new HashMap<Thread, Object>();
	
	public Object get(){
		Thread t=Thread.currentThread();
		return map.get(t);
	}
	public void set(Object obj){
		map.put(Thread.currentThread(), obj);
	}
}


同样可以得出一样的结果,但是仅仅只是模拟了表面功能!


动态代理模板

前面已经贴出代码,但是这里用到了类反射、注解、泛型和动态代理,这几个知识点不是很熟,所以在这里再写遍,提醒一下自己!

 

TxProxy.java

package cn.hncu.utils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;

public class TxProxy implements InvocationHandler {
	private Object srcObj=null;
	private TxProxy(Object srcObj) {
		this.srcObj=srcObj;
	}
	
	public static<T> T getProxy(T srcObj){
		Object newObj=Proxy.newProxyInstance(
				TxProxy.class.getClassLoader(), 
				srcObj.getClass().getInterfaces(), 
				new TxProxy(srcObj));
		return (T)newObj;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		if(method.isAnnotationPresent(Transaction.class)){
			System.out.println("此方法存在注解,进行拦截......");
			Connection con=null;
			Object returnObj=null;
			
			try {
				con=ConnUtils5.getConn();
				con.setAutoCommit(false);
				System.out.println("开启事务.........");
				
				returnObj=method.invoke(srcObj, args);//放行
				
				con.commit();//
				System.out.println("提交事务.......");
			} catch (Exception e) {
				con.rollback();
				System.out.println("回滚事务.........");
			}finally{
				try {
					con.setAutoCommit(true);
					con.close();
				} catch (Exception e) {
					throw new RuntimeException(e.getMessage(), e);
				}
			}
			
			return returnObj;
		}
		System.out.println("没有注解,直接放行....");
		return method.invoke(srcObj, args);
	}
	
}


 

 

 

 

 

  • 9
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值