JAVA开发之简化Dao层、提高开发效率

通常我们在开发Java企业级应用的时候使用的技术大部分是Spring、Hibernate、mybatis、Struts2等。尤其是Spring,相信这个庞大而优雅的技术体系你已经离不开了,在我们项目代码中基本是骨干力量的存在。

而我们使用的ORM框架大多数也是Hibernate、mybatis或者Spring提供的jdbc简单封装的JdbcTemplate。如果我们的项目组开发人员对所选型的ORM框架很熟悉并能熟练使用是再好不过了,但是大部分情况下项目组成员的技术水平是参差不齐的,有时会踩到所选型ORM框架的坑。比如Hibernate很容易引起性能问题,mybatis在重构时很麻烦等等增加了项目的交付风险。

本文希望能在Dao层的开发上做的简单,健壮,提高开发效率:

1 对JdbcTemplate做一层简单的封装,能够对单表的CRUD做到无需写SQL语句

2 在重构SQL代码时对SQL层做少量修改或者不修改

我们开始吧!

我们先考虑一下:如果做到CRUD的不需要写SQL,这也意味着SQL是要自动生成的。

我们先看SQL语句的组成:

1 添加语句: insert into tableName (id, name, ...) values (1, 'name', ...);

2 修改语句:update tableName set name = 'name', age = 'age' where id = 'id';

3 删除语句:delete from tableName where id = 'id';

4 查询语句:select id, name from tableName where age > 18;

一般我们做JAVA企业级开发都会有领域模型的概念,这个领域模型会对应一个数据库表并包括这个数据库表的所有字段。那么这时我们可以利用领域模型的字段生成对应的SQL语句。这里我们先借用JPA的注解完成领域模型属性与数据库表的映射,熟悉hibernate的朋友一定不会陌生,当然你也可以自己定义注解。

如下领域模型:

package com.applet.model;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.applet.base.BaseModel;
import com.applet.enumeration.YesNoEnum;
import com.applet.utils.DateUtils;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Entity
@Table(name = "TS_ROLE")
public class Role implements Serializable {

    /**
     * <p>
     * Field serialVersionUID: 序列号
     * </p>
     */
    private static final long serialVersionUID = 1L;

    //主键
    @Id
    @Column
    @JsonSerialize(using = ToStringSerializer.class)
    protected Long id;

    // 名称
    @Column
    private String name;

    // 状态(1启用,2停用)
    @Column
    private Integer state;

    // 创建人id
    @Column
    @JsonSerialize(using = ToStringSerializer.class)
    private Long createId;

    // 创建日期
    @Temporal(TemporalType.TIMESTAMP)
    @Column
    private Date createTime;

    // 是否系统角色(1是,2否)
    @Column
    private Integer isSys;

    // 描述
    @Column
    private String remark;

    public Long getId() {
        return id;
    }

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

    public String getStateStr() {
        return YesNoEnum.valueOfValidateLabel(state);
    }

    public String getIsSysStr() {
        return YesNoEnum.valueOf(isSys);
    }

    public String getCreateTimeStr() {
        if (createTime != null) {
            return DateUtils.dateTimeToString(createTime);
        }
        return null;
    }

    public String getName() {
        return name;
    }

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

    public Integer getState() {
        return state;
    }

    public void setState(Integer state) {
        this.state = state;
    }

    public Long getCreateId() {
        return createId;
    }

    public void setCreateId(Long createId) {
        this.createId = createId;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Integer getIsSys() {
        return isSys;
    }

    public void setIsSys(Integer isSys) {
        this.isSys = isSys;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }
}

我们的基础的dao接口如下:

package com.applet.base;

import java.io.Serializable;
import java.util.List;

public interface BaseDao<T extends BaseModel, PK extends Serializable>{
    
    /**
     * 判断某一字段是否重复
     * @param id 实体id
     * @param filedValue 字段值
     * @param fieldName 字段名称
     * @return
     */
    //public boolean isDuplicateField(PK id, Object filedValue, String fieldName);
    
    /**
     * <p>Description: 添加实体</p>
     * @param t 实体对象
     */
    public int insert(T t);

    /**
     * <p>Description: 批量添加实体</p>
     * @param list 实体对象列表
     */
    public int batchInsert(final List<T> list);

    /**
     * <p>Description: 更新实体,字段值为null的不更新</p>
     * @param t 实体对象
     */
    public int update(T t);

    /**
     * <p>Description: 更新实体</p>
     * @param t 实体对象
     */
    public int updateForce(T t);

    /**
     * <p>Description: 根据id删除实体</p>
     * @param id 实体id值
     */
    public int delete(PK id);

    /**
     * <p>Description: 批量删除实体</p>
     * @param ids 实体id值数组
     */
    public int delete(PK[] ids);

    /**
     * <p>Description: 按条件查询实体列表</p>
     * @param wb QueryCondition对象
     * @return 实体列表
     */
    //public List<T> query(QueryCondition wb);

    /**
     * <p>Description: 按条件查询实体数量</p>
     * @param wb QueryCondition对象
     * @return 实体数量
     */
    //public int count(QueryCondition wb);

    /**
     * <p>Description: 根据id查询实体</p>
     * @param id 实体id值
     * @return 实体对象
     */
    public T load(PK id);

    /**
     * <p>Description: 按条件删除实体</p>
     * @param wb QueryCondition对象
     */
    //public int deleteByCondition(QueryCondition wb);

    /**
     * <p>Description: 分页查询</p>
     * @param wb QueryCondition对象
     * @return
     */
    //public Page<T> queryPage(QueryCondition wb);
}

我们基础的dao接口实现如下:

package com.applet.base;

import com.applet.sql.builder.SelectBuilder;
import com.applet.sql.builder.WhereBuilder;
import com.applet.sql.mapper.DefaultRowMapper;
import com.applet.sql.page.PageSql;
import com.applet.sql.record.DomainModelAnalysis;
import com.applet.sql.record.DomainModelContext;
import com.applet.sql.record.ExtendType;
import com.applet.sql.record.TableColumn;
import com.applet.sql.type.JdbcType;
import com.applet.sql.type.TypeHandler;
import com.applet.sql.type.TypeHandlerRegistry;
import com.applet.utils.KeyUtils;
import com.applet.utils.Page;
import com.applet.utils.SpringContextHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.ReflectionUtils;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class BaseDaoImpl<T extends BaseModel, PK extends Serializable> implements BaseDao<T, PK>, InitializingBean {

    protected static final Logger log = Logger.getLogger(BaseDaoImpl.class);

    @Autowired
    protected JdbcTemplate jdbcTemplate;
    @Autowired
    protected PageSql pageSql;

    protected Class<T> modelClass;
    protected DomainModelAnalysis domainModelAnalysis;

    public BaseDaoImpl() {
    }

    @SuppressWarnings("unchecked")
    protected Class<T> autoGetDomainClass() {
        if (modelClass == null) {
            Type type = this.getClass().getGenericSuperclass();
            if (type instanceof ParameterizedType) {
                modelClass = (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
            } else {
                throw new RuntimeException("SubClass must give the ActualTypeArguments");
            }
        }
        return modelClass;
    }

    /**
     * 获取添加实体的SQL语句
     *
     * @return
     */
    protected String getInsertSql() {
        String[] array = domainModelAnalysis.joinColumnWithPlaceholder(", ");
        String sql = String.format("INSERT INTO %s (%s) VALUES (%s)", domainModelAnalysis.getTableName(), array[0], array[1]);
        return sql;
    }

    /**
     * 将完整的SQL转换为统计SQL
     * 如:select a, b, c, t.d from table t
     * 转换后为:select count(1) from table t
     *
     * @param sql
     * @return
     */
    protected String toCountSql(String sql) {
        if (StringUtils.isEmpty(sql)) {
            return null;
        }
        return sql.replaceFirst("(?<=(?i)SELECT).*?(?=(?i)FROM)", " COUNT\\(1\\) ").replaceAll("(?=(?i)order).*", "");
    }

    /**
     * 添加实体
     *
     * @param t 实体对象
     * @return
     */
    @Override
    public int insert(T t) {
        List<TableColumn> tableColumnList = domainModelAnalysis.getTableColumnList();
        List<Object> list = new ArrayList<Object>();
        for (int i = 0, size = tableColumnList.size(); i < size; i++) {
            TableColumn tableColumn = tableColumnList.get(i);
            if (tableColumn.isTransient()) {
                continue;
            }
            Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
            ExtendType extendType = tableColumn.getExtendType();
            if (extendType != null && extendType.getCode() != ExtendType.DEFAULT.getCode()) {
                value = value.toString();
            }
            list.add(value);
        }
        return jdbcTemplate.update(getInsertSql(), list.toArray(new Object[0]));
    }

    /**
     * 批量添加实体
     *
     * @param list 实体对象列表
     * @return
     */
    @Override
    public int batchInsert(final List<T> list) {
        final List<TableColumn> tableColumnList = domainModelAnalysis.getTableColumnList();
        final TypeHandlerRegistry typeHandlerRegistry = DomainModelContext.getTypeHandlerRegistry();
        return jdbcTemplate.batchUpdate(getInsertSql(), new BatchPreparedStatementSetter() {
            @SuppressWarnings({"rawtypes", "unchecked"})
            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                T t = list.get(i);
                int index = 1;
                for (int k = 0, size = tableColumnList.size(); k < size; k++) {
                    TableColumn tableColumn = tableColumnList.get(k);
                    if (tableColumn.isTransient()) {
                        continue;
                    }
                    Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
                    TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(tableColumn.getJavaType());
                    typeHandler.setParameter(ps, index, value, JdbcType.NULL);
                    index++;
                }
            }

            @Override
            public int getBatchSize() {
                return list.size();
            }
        }).length;
    }

    /**
     * 更新实体中的非空(不含null, "")字段
     *
     * @param t 实体对象
     * @return
     */
    @Override
    public int update(T t) {
        WhereBuilder wb = new WhereBuilder();
        wb.andEquals(domainModelAnalysis.getPrimaryKey(), t.getId());
        return updateByCondition(t, wb);
    }

    protected int updateByCondition(T t, WhereBuilder whereBuilder) {
        StringBuilder sqlBuilder = new StringBuilder(String.format("UPDATE %s SET ", domainModelAnalysis.getTableName()));
        List<TableColumn> tableColumnList = domainModelAnalysis.getTableColumnList();

        Method primaryKeyMethod = null;
        List<Object> values = new ArrayList<Object>();
        for (int i = 0, size = tableColumnList.size(); i < size; i++) {
            final TableColumn tableColumn = tableColumnList.get(i);
            if (tableColumn.isTransient()) {
                continue;
            }
            if (tableColumn.isPrimaryKey()) {
                primaryKeyMethod = tableColumn.getFieldGetMethod();
                continue;
            }
            Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
            ExtendType extendType = tableColumn.getExtendType();
            if (extendType != null && extendType.getCode() != ExtendType.DEFAULT.getCode()) {
                value = value.toString();
            }
            if (value == null || (value instanceof String && StringUtils.isEmpty((String) value))) {
                continue;
            }

            values.add(value);
            sqlBuilder.append(tableColumn.getColumnName())
                    .append(" = ")
                    .append(tableColumn.getPlaceholder())
                    .append(", ");
        }

        if (values.size() > 0) {
            int length = sqlBuilder.length();
            sqlBuilder.delete(length - 2, length);
            String sql = sqlBuilder.toString();
            sql = whereBuilder.getSql(sql);
            values.addAll(whereBuilder.getParameterList());
            return jdbcTemplate.update(sql, values.toArray(new Object[0]));
        }
        return 0;
    }

    /**
     * 更新实体所有字段
     *
     * @param t 实体对象
     * @return
     */
    @Override
    public int updateForce(T t) {
        StringBuilder sqlBuilder = new StringBuilder(String.format("UPDATE %s SET ", domainModelAnalysis.getTableName()));
        List<TableColumn> tableColumnList = domainModelAnalysis.getTableColumnList();

        Method primaryKeyMethod = null;
        List<Object> values = new ArrayList<Object>();
        for (int i = 0, size = tableColumnList.size(); i < size; i++) {
            final TableColumn tableColumn = tableColumnList.get(i);
            if (tableColumn.isTransient()) {
                continue;
            }
            if (tableColumn.isPrimaryKey()) {
                primaryKeyMethod = tableColumn.getFieldGetMethod();
                continue;
            }
            Object value = ReflectionUtils.invokeMethod(tableColumn.getFieldGetMethod(), t);
            values.add(value);
            sqlBuilder.append(tableColumn.getColumnName())
                    .append(" = ")
                    .append(tableColumn.getPlaceholder())
                    .append(", ");
        }

        int length = sqlBuilder.length();
        sqlBuilder.delete(length - 2, length);
        sqlBuilder.append(" WHERE ").append(domainModelAnalysis.getPrimaryKey()).append(" = ?");
        values.add(ReflectionUtils.invokeMethod(primaryKeyMethod, t));
        return jdbcTemplate.update(sqlBuilder.toString(), values.toArray(new Object[0]));
    }

    /**
     * 根据主键删除实体
     *
     * @param id 实体主键值
     * @return
     */
    @Override
    public int delete(PK id) {
        String sql = String.format("DELETE FROM %s WHERE %s = ?", domainModelAnalysis.getTableName(), domainModelAnalysis.getPrimaryKey());
        return jdbcTemplate.update(sql, new Object[]{id});
    }

    /**
     * 根据主键删除实体
     *
     * @param ids 实体主键值数组
     * @return
     */
    @Override
    public int delete(final PK[] ids) {
        String sql = String.format("DELETE FROM %s WHERE %s = ?", domainModelAnalysis.getTableName(), domainModelAnalysis.getPrimaryKey());
        int[] batchUpdate = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {

            @Override
            public void setValues(PreparedStatement ps, int i) throws SQLException {
                ps.setObject(1, ids[i]);
            }

            @Override
            public int getBatchSize() {
                return ids.length;
            }
        });
        return batchUpdate.length;
    }

    /**
     * 查询实体列表
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected List<T> query(WhereBuilder wb) {
        String sql = String.format("SELECT %s FROM %s", domainModelAnalysis.getDefaultColumnArrayStr(), domainModelAnalysis.getTableName());
        Object args[] = null;
        if (wb != null) {
            sql = wb.getSql(sql);
            args = wb.getParameters();
        }
        return jdbcTemplate.query(sql, args, new DefaultRowMapper<T>(domainModelAnalysis));
    }

    /**
     * 查询实体列表
     *
     * @param sql select语句
     * @param wb  where语句拼接实例
     * @return
     */
    protected List<T> query(String sql, WhereBuilder wb) {
        Object args[] = null;
        if (wb != null) {
            sql = wb.getSql(sql);
            args = wb.getParameters();
        }
        return jdbcTemplate.query(sql, args, new DefaultRowMapper<T>(domainModelAnalysis));
    }

    /**
     * 限制返回查询记录数量
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected List<T> queryLimit(WhereBuilder wb) {
        String sql = String.format("SELECT %s FROM %s", domainModelAnalysis.getDefaultColumnArrayStr(), domainModelAnalysis.getTableName());
        return queryLimit(sql, wb);
    }

    /**
     * 限制返回查询记录数量
     *
     * @param querySql select语句
     * @param wb       where语句拼接实例
     * @return
     */
    protected List<T> queryLimit(String querySql, WhereBuilder wb) {
        Object args[] = null;
        int pageNum = 0, pageSize = 0;
        if (wb != null) {
            pageNum = wb.getPageNum();
            pageSize = wb.getPageSize();
            querySql = wb.getSql(querySql);
            args = wb.getParameters();
        }
        String qsql = pageSql.getSql(querySql, pageNum, pageSize);
        List<T> list = jdbcTemplate.query(qsql, args, new DefaultRowMapper<T>(domainModelAnalysis));
        return list;
    }

    /**
     * 按条件统计实体数量
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected int count(WhereBuilder wb) {
        String sql = String.format("SELECT COUNT(1) FROM %s", domainModelAnalysis.getTableName());
        Object args[] = null;
        if (wb != null) {
            sql = wb.getSql(sql);
            args = wb.getParameters();
        }
        return jdbcTemplate.queryForObject(sql, args, Integer.class);
    }

    /**
     * 根据主键查询实体
     *
     * @param id 实体主键值
     * @return
     */
    @Override
    public T load(PK id) {
        String sql = String.format("SELECT %s FROM %s WHERE %s = ?", domainModelAnalysis.getDefaultColumnArrayStr(), domainModelAnalysis.getTableName(), domainModelAnalysis.getPrimaryKey());
        List<T> list = jdbcTemplate.query(sql, new Object[]{id}, new DefaultRowMapper<T>(domainModelAnalysis));
        return DataAccessUtils.singleResult(list);
    }

    /**
     * 按条件删除实体
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected int deleteByCondition(WhereBuilder wb) {
        String sql = String.format("DELETE FROM %s", domainModelAnalysis.getTableName());
        Object args[] = null;
        if (wb != null) {
            sql = wb.getSql(sql);
            args = wb.getParameters();
        }
        return jdbcTemplate.update(sql, args);
    }

    /**
     * 分页查询
     *
     * @param wb where语句拼接实例
     * @return
     */
    protected Page<T> queryPage(WhereBuilder wb) {
        String sql = String.format("SELECT %s FROM %s ", domainModelAnalysis.getDefaultColumnArrayStr(), domainModelAnalysis.getTableName());
        return queryPage(sql, wb);
    }

    /**
     * 分页查询
     *
     * @param sql select语句
     * @param wb  where语句拼接实例
     * @return
     */
    protected Page<T> queryPage(String sql, WhereBuilder wb) {
        String countSql = toCountSql(sql);
        return queryPage(sql, countSql, wb);
    }

    /**
     * 分页查询
     *
     * @param querySql select语句
     * @param countSql count语句
     * @param wb       where语句拼接实例
     * @return
     */
    protected Page<T> queryPage(String querySql, String countSql, WhereBuilder wb) {
        Object args[] = null;
        int pageNum = 0, pageSize = 0;
        if (wb != null) {
            querySql = wb.getSql(querySql);
            args = wb.getParameters();
            pageSize = wb.getPageSize();
            pageNum = wb.getPageNum();
        }
        String qsql = pageSql.getSql(querySql, pageNum, pageSize);
        List<T> list = jdbcTemplate.query(qsql, args, new DefaultRowMapper<T>(domainModelAnalysis));

        Page<T> page = new Page<T>();
        page.setData(list);

        if (StringUtils.isNotEmpty(countSql)) {
            String csql = wb.getCountSql(countSql);
            long count = jdbcTemplate.queryForObject(csql, args, Long.class);
            page.setTotal(count);
        }
        return page;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        DomainModelContext domainModelContext = SpringContextHelper.getBean(DomainModelContext.class);
        domainModelAnalysis = domainModelContext.registerBean(autoGetDomainClass());
    }
}

这里最关键的是如何解析领域模型的属性信息,我们可以在运行时通过反射把领域模型的属性信息解析出来并全局缓存起来。这步操作是在BaseDaoImpl.java的如下代码完成的---如果你不熟悉InitializingBean接口,可以搜索一下它的意义:

@Override
public void afterPropertiesSet() throws Exception {
        DomainModelContext domainModelContext = SpringContextHelper.getBean(DomainModelContext.class);
        domainModelAnalysis = domainModelContext.registerBean(autoGetDomainClass());
}

下一节为大家介绍如何使用Java反射把领域模型的属性信息解析出来并全局缓存起来。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • 一. Java基础部分.................................................
    wy_sure阅读 3,789评论 0 11
  • 这部分主要是开源Java EE框架方面的内容,包括Hibernate、MyBatis、Spring、Spring ...
    杂货铺老板阅读 1,343评论 0 2
  • Mybatis相关 1.Mybatis是什么? 2.为什么选择Mybatis? 3、#{}和${}的区别是什么? ...
    zhihaoZzz阅读 1,274评论 0 2
  • '我要结婚了'。 小红看着闪烁的企鹅点开之后弹出的对话框,心里一阵轻松,蓦地心头又一紧,眼眶热热的。王浩,我们以后...
    小李崽_阅读 339评论 0 1