• 注解+反射+JDBC,实现一个简易的泛型DAO接口


    一、实现思路

           1、定义3个Annotation(注解):Entity、Id、Column,Entity作用于Type级别,用于标识JavaBean与数据库表名的映射关系。Id作用于Field级别,用于标识JavaBean中ID属性与表中ID字段的映射关系,Column作用于Field级别,用于标识JavaBean中除ID属性外的其它属性与表中字段的映射关系。

         2、在Dao实现类中,通过反射API获得JavaBean中注解和属性的信息,如:表名、字段。JavaBean属性的名称、数据类型等信息。然后将这些信息拼接成一条SQL语句,通过JDBC的方式与数据库交互。

    二、示例代码

    1、定义一个Dao公共类,提供获得数据库连接与释放数据库资源的接口

    package dao;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * 提供获取数据库连接、释放资源的接口
     */
    public class JdbcDaoHelper {
    	
    	/**
    	 * 数据库用户名
    	 */
    	private static final String USER = "test";
    	
    	/**
    	 * 数据库密码 
    	 */
    	private static final String PASSWORD = "test";
    	
    	/**
    	 * 连接数据库的地址
    	 */
    	private static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521:study";
    	
    	private static Connection conn;
    	
    	/**
    	 * 获得一个数据库连接对象
    	 * @return java.sql.Connection实例
    	 */
    	public static Connection getConnection() {
    		try {
    			if (conn == null) {
    				Class.forName("oracle.jdbc.OracleDriver");
    				conn = DriverManager.getConnection(URL, USER, PASSWORD);
    			} else {
    				return conn;
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		return conn;
    	}
    	
    	/**
    	 * 释放数据库资源
    	 */
    	public static void release(PreparedStatement ps,ResultSet rs) {
    		try {
    			if (conn != null) {
    				conn.close();
    				conn = null;
    			}
    			if (ps != null) {
    				ps.close();
    				ps = null;
    			}
    			if (rs != null) {
    				rs.close();
    				rs = null;
    			}
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    	}
    }

    2、定义一个泛型Dao接口GenericDao<T>

    package dao;
    
    import java.util.List;
    import java.util.Map;
    
    public interface GenericDao<T> {
    	
    	public void save(T t) throws Exception;
    	
    	public void delete(Object id,Class<T> clazz) throws Exception;
    	
    	public void update(T t) throws Exception;
    	
    	public T get(Object id,Class<T> clazz) throws Exception;
    	
    	/**
    	 * 根据条件查询
    	 * @param sqlWhereMap key:条件字段名 value:条件字段值
    	 * @param clazz
    	 * @return
    	 * @throws Exception
    	 */
    	public List<T> findAllByConditions(Map<String,Object> sqlWhereMap,Class<T> clazz) throws Exception;
    	
    }

    3、定义GenericDao<T>接口JDBC实现类JdbcGenericDaoImpl<T>

    package dao;
    
    import java.beans.IntrospectionException;
    import java.beans.PropertyDescriptor;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Timestamp;
    import java.sql.Types;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.Map.Entry;
    
    import annotation.Column;
    import annotation.Entity;
    import annotation.Id;
    import exception.NotFoundAnnotationException;
    
    /**
     * 泛型DAO的JDBC实现
     * @author 杨信
     * @version 1.0
     */
    public class JdbcGenericDaoImpl<T> implements GenericDao<T> {
    	
    	//表的别名
    	private static final String TABLE_ALIAS = "t";
    
    	@Override
    	public void save(T t) throws Exception {
    		Class<?> clazz = t.getClass();
    		//获得表名
    		String tableName = getTableName(clazz);
    		//获得字段
    		StringBuilder fieldNames = new StringBuilder();		//字段名
    		List<Object> fieldValues = new ArrayList<Object>();	//字段值
    		StringBuilder placeholders = new StringBuilder();	//占位符
    		Field[] fields = clazz.getDeclaredFields();
    		for (Field field : fields) {
    			PropertyDescriptor pd = new PropertyDescriptor(field.getName(),t.getClass());
    			if (field.isAnnotationPresent(Id.class)) {
    				fieldNames.append(field.getAnnotation(Id.class).value()).append(",");
    				fieldValues.add(pd.getReadMethod().invoke(t));
    			} else if(field.isAnnotationPresent(Column.class)) {
    				fieldNames.append(field.getAnnotation(Column.class).value()).append(",");
    				fieldValues.add(pd.getReadMethod().invoke(t));
    			}
    			placeholders.append("?").append(",");
    		}
    		//删除最后一个逗号
    		fieldNames.deleteCharAt(fieldNames.length()-1);
    		placeholders.deleteCharAt(placeholders.length()-1);
    		
    		//拼接sql
    		StringBuilder sql = new StringBuilder("");
    		sql.append("insert into ").append(tableName)
    		   .append(" (").append(fieldNames.toString())
    		   .append(") values (").append(placeholders).append(")") ;
    		PreparedStatement ps = JdbcDaoHelper.getConnection().prepareStatement(sql.toString());
    		//设置SQL参数占位符的值
    		setParameter(fieldValues, ps, false);
    		//执行SQL
    		ps.execute();
    		JdbcDaoHelper.release(ps, null);
    		
    		System.out.println(sql + "\n" + clazz.getSimpleName() + "添加成功!");
    	}
    
    
    	@Override
    	public void delete(Object id,Class<T> clazz) throws Exception {
    		//获得表名
    		String tableName = getTableName(clazz);
    		//获得ID字段名和值
    		String idFieldName = "";
    		boolean flag = false;
    		Field[] fields = clazz.getDeclaredFields();
    		for (Field field : fields) {
    			if(field.isAnnotationPresent(Id.class)) {
    				idFieldName = field.getAnnotation(Id.class).value();
    				flag = true;
    				break;
    			}
    		}
    		if (!flag) {
    			throw new NotFoundAnnotationException(clazz.getName() + " object not found id property.");
    		}
    		
    		//拼装sql
    		String sql = "delete from " + tableName + " where " + idFieldName + "=?";
    		PreparedStatement ps = JdbcDaoHelper.getConnection().prepareStatement(sql);
    		ps.setObject(1, id);
    		//执行SQL
    		ps.execute();
    		JdbcDaoHelper.release(ps,null);
    		
    		System.out.println(sql + "\n" + clazz.getSimpleName() + "删除成功!");
    	}
    
    	@Override
    	public void update(T t) throws Exception {
    		Class<?> clazz = t.getClass();
    		//获得表名
    		String tableName = getTableName(clazz);
    		//获得字段
    		List<Object> fieldNames = new ArrayList<Object>();	//字段名
    		List<Object> fieldValues = new ArrayList<Object>();	//字段值
    		List<String> placeholders = new ArrayList<String>();//占位符
    		String idFieldName = "";
    		Object idFieldValue = "";
    		Field[] fields = clazz.getDeclaredFields();
    		for (Field field : fields) {
    			PropertyDescriptor pd = new PropertyDescriptor(field.getName(),t.getClass());
    			if (field.isAnnotationPresent(Id.class)) {
    				idFieldName = field.getAnnotation(Id.class).value();
    				idFieldValue = pd.getReadMethod().invoke(t);
    			} else if(field.isAnnotationPresent(Column.class)) {
    				fieldNames.add(field.getAnnotation(Column.class).value());
    				fieldValues.add(pd.getReadMethod().invoke(t));
    				placeholders.add("?");
    			}
    		}
    		//ID作为更新条件,放在集合中的最后一个元素
    		fieldNames.add(idFieldName);
    		fieldValues.add(idFieldValue);
    		placeholders.add("?");
    		
    		//拼接sql
    		StringBuilder sql = new StringBuilder("");
    		sql.append("update ").append(tableName).append(" set ");
    		int index = fieldNames.size() - 1;
    		for (int i = 0; i < index; i++) {
    			sql.append(fieldNames.get(i)).append("=").append(placeholders.get(i)).append(",");
    		}
    		sql.deleteCharAt(sql.length()-1).append(" where ").append(fieldNames.get(index)).append("=").append("?");
    		
    		//设置SQL参数占位符的值
    		PreparedStatement ps = JdbcDaoHelper.getConnection().prepareStatement(sql.toString());
    		setParameter(fieldValues, ps, false);
    		
    		//执行SQL
    		ps.execute();
    		JdbcDaoHelper.release(ps, null);
    		
    		System.out.println(sql + "\n" + clazz.getSimpleName() + "修改成功.");
    	}
    
    	@Override
    	public T get(Object id,Class<T> clazz) throws Exception {
    		String idFieldName = "";
    		Field[] fields = clazz.getDeclaredFields();
    		boolean flag = false;
    		for (Field field : fields) {
    			if (field.isAnnotationPresent(Id.class)) {
    				idFieldName = field.getAnnotation(Id.class).value();
    				flag = true;
    				break;
    			} 
    		}
    		
    		if (!flag) {
    			throw new NotFoundAnnotationException(clazz.getName() + " object not found id property.");
    		}
    		
    		//拼装SQL
    		Map<String,Object> sqlWhereMap = new HashMap<String, Object>();
    		sqlWhereMap.put(TABLE_ALIAS + "." + idFieldName, id);
    		
    		List<T> list = findAllByConditions(sqlWhereMap, clazz);
    		return list.size() > 0 ? list.get(0) : null;
    	}
    
    	@Override
    	public List<T> findAllByConditions(Map<String,Object> sqlWhereMap,Class<T> clazz) throws Exception {
    		List<T> list = new ArrayList<T>();
    		String tableName = getTableName(clazz);
    		String idFieldName = "";
    		//存储所有字段的信息
    		//通过反射获得要查询的字段
    		StringBuffer fieldNames = new StringBuffer();
    		Field[] fields = clazz.getDeclaredFields();
    		for (Field field : fields) {
    			String propertyName = field.getName();
    			if (field.isAnnotationPresent(Id.class)) {
    				idFieldName = field.getAnnotation(Id.class).value();
    				fieldNames.append(TABLE_ALIAS + "." + idFieldName)
    						  .append(" as ").append(propertyName).append(",");
    			} else if (field.isAnnotationPresent(Column.class)) {
    				fieldNames.append(TABLE_ALIAS + "." + field.getAnnotation(Column.class).value())
    						  .append(" as ").append(propertyName).append(",");
    			}
    		}
    		fieldNames.deleteCharAt(fieldNames.length()-1);
    		
    		//拼装SQL
    		String sql = "select " + fieldNames + " from " + tableName + " " + TABLE_ALIAS;
    		PreparedStatement ps = null;
    		List<Object> values = null;
    		if (sqlWhereMap != null) {
    			List<Object> sqlWhereWithValues = getSqlWhereWithValues(sqlWhereMap);
    			if (sqlWhereWithValues != null) {
    				//拼接SQL条件
    				String sqlWhere = (String)sqlWhereWithValues.get(0);
    				sql += sqlWhere;
    				//得到SQL条件中占位符的值
    				values = (List<Object>) sqlWhereWithValues.get(1);
    			}
    		} 
    		
    		//设置参数占位符的值
    		if (values != null) {
    			ps = JdbcDaoHelper.getConnection().prepareStatement(sql);
    			setParameter(values, ps, true);
    		} else {
    			ps = JdbcDaoHelper.getConnection().prepareStatement(sql);
    		}
    		
    		
    		//执行SQL
    		ResultSet rs = ps.executeQuery();
    		while(rs.next()) {
    			T t = clazz.newInstance();
    			initObject(t, fields, rs);
    			list.add(t);
    		}
    		
    		//释放资源
    		JdbcDaoHelper.release(ps, rs);
    		
    		System.out.println(sql);
    		return list;
    	}
    	
    	/**
    	 * 根据结果集初始化对象
    	 */
    	private void initObject(T t, Field[] fields, ResultSet rs)
    			throws SQLException, IntrospectionException,
    			IllegalAccessException, InvocationTargetException {
    		for (Field field : fields) {
    			String propertyName = field.getName();
    			Object paramVal = null;
    			Class<?> clazzField = field.getType();
    			if (clazzField == String.class) {
    				paramVal = rs.getString(propertyName);
    			} else if (clazzField == short.class || clazzField == Short.class) {
    				paramVal = rs.getShort(propertyName);
    			} else if (clazzField == int.class || clazzField == Integer.class) {
    				paramVal = rs.getInt(propertyName);
    			} else if (clazzField == long.class || clazzField == Long.class) {
    				paramVal = rs.getLong(propertyName);
    			} else if (clazzField == float.class || clazzField == Float.class) {
    				paramVal = rs.getFloat(propertyName);
    			} else if (clazzField == double.class || clazzField == Double.class) {
    				paramVal = rs.getDouble(propertyName);
    			} else if (clazzField == boolean.class || clazzField == Boolean.class) {
    				paramVal = rs.getBoolean(propertyName);
    			} else if (clazzField == byte.class || clazzField == Byte.class) {
    				paramVal = rs.getByte(propertyName);
    			} else if (clazzField == char.class || clazzField == Character.class) {
    				paramVal = rs.getCharacterStream(propertyName);
    			} else if (clazzField == Date.class) {
    				paramVal = rs.getTimestamp(propertyName);
    			} else if (clazzField.isArray()) {
    				paramVal = rs.getString(propertyName).split(",");	//以逗号分隔的字符串
    			} 
    			PropertyDescriptor pd = new PropertyDescriptor(propertyName,t.getClass());
    			pd.getWriteMethod().invoke(t, paramVal);
    		}
    	}
    	
    	/**
    	 * 根据条件,返回sql条件和条件中占位符的值
    	 * @param sqlWhereMap key:字段名 value:字段值
    	 * @return 第一个元素为SQL条件,第二个元素为SQL条件中占位符的值
    	 */
    	private List<Object> getSqlWhereWithValues(Map<String,Object> sqlWhereMap) {
    		if (sqlWhereMap.size() <1 ) return null;
    		List<Object> list = new ArrayList<Object>();
    		List<Object> fieldValues = new ArrayList<Object>();
    		StringBuffer sqlWhere = new StringBuffer(" where ");
    		Set<Entry<String, Object>> entrySets = sqlWhereMap.entrySet();
    		for (Iterator<Entry<String, Object>> iteraotr = entrySets.iterator();iteraotr.hasNext();) {
    			Entry<String, Object> entrySet = iteraotr.next();
    			fieldValues.add(entrySet.getValue());
    			Object value = entrySet.getValue();
    			if (value.getClass() == String.class) {
    				sqlWhere.append(entrySet.getKey()).append(" like ").append("?").append(" and ");
    			} else {
    				sqlWhere.append(entrySet.getKey()).append("=").append("?").append(" and ");
    			}
    		}
    		sqlWhere.delete(sqlWhere.lastIndexOf("and"), sqlWhere.length());
    		list.add(sqlWhere.toString());
    		list.add(fieldValues);
    		return list;
    	}
    	
    	
    	/**
    	 * 获得表名
    	 */
    	private String getTableName(Class<?> clazz) throws NotFoundAnnotationException {
    		if (clazz.isAnnotationPresent(Entity.class)) {
    			Entity entity = clazz.getAnnotation(Entity.class);
    			return entity.value();
    		} else {
    			throw new NotFoundAnnotationException(clazz.getName() + " is not Entity Annotation.");
    		}
    	}
    	
    	/**
    	 * 设置SQL参数占位符的值
    	 */
    	private void setParameter(List<Object> values, PreparedStatement ps, boolean isSearch)
    			throws SQLException {
    		for (int i = 1; i <= values.size(); i++) {
    			Object fieldValue = values.get(i-1);
    			Class<?> clazzValue = fieldValue.getClass();
    			if (clazzValue == String.class) {
    				if (isSearch) 
    					ps.setString(i, "%" + (String)fieldValue + "%");
    				else
    					ps.setString(i,(String)fieldValue);
    					
    			} else if (clazzValue == boolean.class || clazzValue == Boolean.class) {
    				ps.setBoolean(i, (Boolean)fieldValue);
    			} else if (clazzValue == byte.class || clazzValue == Byte.class) {
    				ps.setByte(i, (Byte)fieldValue);
    			} else if (clazzValue == char.class || clazzValue == Character.class) {
    				ps.setObject(i, fieldValue,Types.CHAR);
    			} else if (clazzValue == Date.class) {
    				ps.setTimestamp(i, new Timestamp(((Date) fieldValue).getTime()));
    			} else if (clazzValue.isArray()) {
    				Object[] arrayValue = (Object[]) fieldValue;
    				StringBuffer sb = new StringBuffer();
    				for (int j = 0; j < arrayValue.length; j++) {
    					sb.append(arrayValue[j]).append("、");
    				}
    				ps.setString(i, sb.deleteCharAt(sb.length()-1).toString());
    			} else {
    				ps.setObject(i, fieldValue, Types.NUMERIC);
    			}
    		}
    	}
    }

    4、定义三个注解Entity、Id、Column,生命周期保存在运行期间,以便通过反射获取
    1)、Entity
    package annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 数据库表的的名称
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Entity {
    
    	/**
    	 * 表名
    	 */
    	String value();
    	
    }
    2)、Id
    package annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 标识数据库字段的ID
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Id {
    
    	/**
    	 * ID的名称
    	 * @return
    	 */
    	String value();
    	
    }
    3)、Column
    package annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 标识数据库字段的名称
     * @author 杨信
     *
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface Column {
    	
    	/**
    	 * 字段名称
    	 */
    	String value();
    	
    	/**
    	 * 字段的类型
    	 * @return
    	 */
    	Class<?> type() default String.class;
    	
    	/**
    	 * 字段的长度
    	 * @return
    	 */
    	int length() default 0;
    
    }


    五、定义一个JavaBean,用于测试使用

    要求:

    1)、类名必须用Entity注解标识,并指定数据库中对应的表名

    2)、Id属性必须用Id注解标识,并指定表中所对应的字段名

    3)、其它属性必须用Column注解标识,并指定表中所对应的字段名

    4)、JavaBean属性的数据类型目前只实现了8大基本数据类型、String和这些基本类型的数组类型

    5)、JavaBean属性目前没有做字段的长度与类型的判断,待以后改进。

    package model;
    
    import java.util.Date;
    
    import annotation.Column;
    import annotation.Entity;
    import annotation.Id;
    
    /**
     * 图书
     */
    @Entity("t_book")	//表名
    public class Book {
    
    	/**
    	 * 图书编号
    	 */
    	@Id("t_isbn")
    	private String isbn;
    
    	/**
    	 * 书名
    	 */
    	@Column("t_name")
    	private String name;
    
    	/**
    	 * 作者
    	 */
    	@Column("t_author")
    	private String author;
    
    	/**
    	 * 出版社
    	 */
    	@Column("t_publishing")
    	private String publishing;
    
    	/**
    	 * 出版时间
    	 */
    	@Column(value = "t_pubdate")
    	private Date pubdate;
    
    	/**
    	 * 价格
    	 */
    	@Column(value = "t_price")
    	private double price;
    
    	public String getIsbn() {
    		return isbn;
    	}
    
    	public void setIsbn(String isbn) {
    		this.isbn = isbn;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public String getAuthor() {
    		return author;
    	}
    
    	public void setAuthor(String author) {
    		this.author = author;
    	}
    
    	public String getPublishing() {
    		return publishing;
    	}
    
    	public void setPublishing(String publishing) {
    		this.publishing = publishing;
    	}
    
    	public Date getPubdate() {
    		return pubdate;
    	}
    
    	public void setPubdate(Date pubdate) {
    		this.pubdate = pubdate;
    	}
    
    	public double getPrice() {
    		return price;
    	}
    
    	public void setPrice(double price) {
    		this.price = price;
    	}
    
    	@Override
    	public String toString() {
    		return "书名: " + name + " 图书编号: " + isbn + " 作者: " + author
    				+ " 出版社: " + publishing + " 出版时间: " + pubdate
    				+ " 价格: " + price;
    	}
    }
    

    六、使用Junit4进行单元测试

    package xml;
    
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import model.Book;
    
    import org.junit.BeforeClass;
    import org.junit.Test;
    
    import util.DateUtils;
    import dao.GenericDao;
    import dao.JdbcGenericDaoImpl;
    
    /**
     * 测试泛型DAO的CRUD操作
     */
    public class GenericDaoTest {
    	
    	private GenericDao<Book> bookDao = new JdbcGenericDaoImpl<Book>();
    	
    	private static InputStream is;
    
    	@BeforeClass
    	public static void setUpBeforeClass() throws Exception {
    		is = XmlParserTest.class.getResourceAsStream("/books.xml");
    	}
    	
    	@Test
    	public void testSave() throws Exception {
    		List<Book> books = SaxHelper.saxReader(is);
    		for (Book book : books) {
    			bookDao.save(book);
    		}
    	}
    	
    	@Test
    	public void testStudentFindAll1() throws Exception {
    		System.out.println("\n-------------更新、删除前,测试查询所有记录--------------------");
    		List<Book> books = bookDao.findAllByConditions(null, Book.class);
    		for (Book book : books) {
    			System.out.println(book);
    		}
    	} 
    	
    	@Test
    	public void testDelete() throws Exception {
    		System.out.println("\n-------------测试删除一条记录--------------------");
    		bookDao.delete("9787111349662",Book.class);
    	}
    	
    	@Test
    	public void testGet() throws Exception {
    		System.out.println("\n-------------测试查询一条记录--------------------");
    		Book book = bookDao.get("9787121025389", Book.class);
    		System.out.println(book);
    	}
    	
    	@Test
    	public void testUpdate() throws Exception {
    		System.out.println("\n-------------测试修改一条记录--------------------");
    		Book book = new Book();
    		book.setIsbn("9787121025389");
    		book.setName("JAVA面向对象编程");
    		book.setAuthor("孙卫琴");
    		book.setPublishing("电子工业出版社");
    		book.setPubdate(DateUtils.string2Date("yyyy-MM-dd", "2006-07-01"));
    		book.setPrice(50.6);
    		bookDao.update(book);
    	}
    	
    	@Test
    	public void testStudentFindAll2() throws Exception {
    		System.out.println("\n-------------更新、删除前,测试根据条件查询所有记录--------------------");
    		Map<String,Object> sqlWhereMap = new HashMap<String, Object>();
    		//sqlWhereMap.put("t_isbn", "9787111213826");
    		//sqlWhereMap.put("t_name", "Java");
    		sqlWhereMap.put("t_publishing", "机械工业出版社");
    		//sqlWhereMap.put("t_pubdate", new Date(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2007-01-01 12:06:00").getTime()));
    		List<Book> books = bookDao.findAllByConditions(null, Book.class);
    		for (Book book : books) {
    			System.out.println(book);
    		}
    	} 
    
    }
    七、测试结果

    books.xml请见http://blog.csdn.net/xyang81/article/details/7247169

    说明:功能比较简单,代码写得也比较笨拙,还有待优化,诚请指教!

  • 相关阅读:
    cs231n.stanford.edu
    cs229.stanford.edu
    Boost circular_buffer example
    OI中一些常见实用的套路【更新中】
    用Java读取xml文件内容
    在控制台中操作MYSQL数据库步骤以及一些小问题
    java 实现对指定目录的文件进行下载
    将java项目发布到本地的linux虚拟机上
    mybatis 控制台打印出来的sql 执行结果为空 但是将sql放到mysql执行有数据
    spring mvc 拦截器
  • 原文地址:https://www.cnblogs.com/xyang0917/p/4172536.html
Copyright © 2020-2023  润新知