列数不匹配; SQL 语句 (org.h2.jdbc.JdbcSQLException) - 使用 NamedParameterJdbcTemplate 插入到 table

Column count does not match; SQL statement (org.h2.jdbc.JdbcSQLException) - At inserting to table with NamedParameterJdbcTemplate

我想将 User 实例保存到 H2 数据库。

我在将新用户保存到数据库时遇到以下异常:

Caused by: org.h2.jdbc.JdbcSQLException: Column count does not match; SQL statement:
INSERT INTO Users (user_id, user_name, user_birthday, user_email, user_role, user_tickets) 
VALUES (?,     ?,     ?,     ?,     ?,     ) [21002-191]

这是 DAO 片段:

@Override
public Integer create(User entity) {
    String sql = "INSERT INTO Users (user_id, user_name, user_birthday, user_email, user_role, user_tickets) " +
                                "VALUES (:id,     :name,     :birthday,     :email,     :role,     :tickets)";

    SqlParameterSource parameterSource =
            new MapSqlParameterSource("id", entity.getId())
            .addValue("name", entity.getName())
            .addValue("birthday", entity.getBirthday())
            .addValue("email", entity.getEmail())
            .addValue("role", entity.getRole())
            .addValue("tickets", entity.getBookedTickets());

    Logger.info("Create user: " + entity);
    return getNamedParameterJdbcTemplate().update(sql, parameterSource); <== It fails here 
}

SQL 用于创建数据库的脚本如下所示:

----------------------
-- Create Users table
----------------------
CREATE TABLE Users (
  user_id        INTEGER PRIMARY KEY NOT NULL,
  user_name      VARCHAR(30) NULL,
  user_birthday  DATETIME NULL,
  user_email     VARCHAR(30) NULL,
  user_role      VARCHAR(20) NULL,
  user_tickets   VARCHAR(100) NULL,
);

-----------------------
-- Create Tickets table
-----------------------
CREATE TABLE Tickets (
  tick_id        INTEGER PRIMARY KEY NOT NULL,
  event_id       VARCHAR(30),
  tick_price     DECIMAL(8,2),
  user_id        INTEGER,
);

这里是User POJO:

public class User {
    private Integer id;
    private String name;
    private Calendar birthday;
    private String email;
    private String role;
    private Set<Ticket> bookedTickets = new HashSet<>();
    // getters / setters

我想它不能写入Set<Ticket>,但我不知道如何解决这个问题。

更新:

为了执行数据库访问,我正在使用 - Spring JDBC。 正好 NamedParameterJdbcTemplate:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
<bean class="net.lelyak.edu.dao.NamedParameterJdbcDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

public class NamedParameterJdbcDaoImpl extends NamedParameterJdbcDaoSupport {

    @Autowired
    private DataSource dataSource;

    @PostConstruct
    private void initialize() {
        setDataSource(dataSource);
    }
}

DAO 实现:

@Repository
public class UserDaoImpl extends NamedParameterJdbcDaoImpl implements IGenericDao<User, Integer> {

    @Override
    public Integer create(User entity) {
        // todo move SQL queries to utility class
        String sql = "INSERT INTO Users (user_id, user_name, user_birthday, user_email, user_role, user_tickets) " +
                                    "VALUES (:id,     :name,     :birthday,     :email,     :role,     :tickets)";
    // see create() at above text

有什么建议吗?

user_ticketsVARCHAR(100),但您分配给 :tickets 的值是 Set<Ticket>,那么它应该如何工作?

Spring 不知道你在做什么,但它假设你在使用多值参数时正在构建一个 IN 子句,例如x IN (:tickets),因此它将 :tickets 替换为适当数量的参数标记。例如。如果您的集合有 3 个值,它将变为 x IN (?,?,?).

您的 Set 是空的,因此没有生成任何标记。从技术上讲,我认为它应该抛出异常,因为即使对于 IN 子句也是无效的,但事实并非如此。

那么,如果您的 Set<Ticket> 有值,您期望列 user_tickets 的值是多少? Set 的字符串版本,例如[Ticket1, Ticket2]?如果是,则调用 toString().

.addValue("tickets", entity.getBookedTickets().toString());

然后祈祷不要超过 100 个字符。

对不起,我忘记了。 我的假设是用户可以有很多票,一张票只属于一个用户。您无法将所有集合保存在数据库单元格中,因此解决方案是更改关系并将用户 ID 保存在票证上。贝娄就是全部。我创建了服务 class,检查数据库中是否有用户,如果没有 - 保存它。

@Entity
public class User {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private Calendar birthday;
private String email;
private String role;

@Transient
private Set<Ticket> bookedTickets = new HashSet<>(); //I cant save collection into database

//getters and setters

}

@Entity
public class Ticket {

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String desc;
private int number;
/** id of customer as owner this ticket */
@ManyToOne
private User user;

//getters and setters

}

在userDAO方法中保存:

public void save(User user){
     String sql = "INSERT INTO User ( name, birthday, email, role) VALUES (:name, :birthday, :email, :role)";

     SqlParameterSource parameterSource =
                new MapSqlParameterSource("name", user.getName())
                .addValue("birthday", user.getBirthday())
                .addValue("email", user.getEmail())
                .addValue("role", user.getRole());

     namedParameterJdbcTemplate.update(sql, parameterSource);

     sql="SELECT id FROM User WHERE name = :name AND birthday=:birthday AND email=:email AND role=:role";
     Integer id = namedParameterJdbcTemplate.query(sql, parameterSource, new ResultSetExtractor<Integer>() {
        @Override
        public Integer extractData(ResultSet result) throws SQLException,DataAccessException {
            return result.getInt("id");
        }
    });
    user.setId(id); 
 }

在 ticketDAO 方法中保存:

     public void save(Ticket ticket){
     String sql = "INSERT INTO Ticket  (desc , number, user_id) VALUES (:desc, :number, :userId)";

     SqlParameterSource parameterSource =
                new MapSqlParameterSource("desc", ticket.getDesc())
                .addValue("number", ticket.getNumber())
                .addValue("userId", ticket.getUser().getId());

     namedParameterJdbcTemplate.update(sql, parameterSource);
 }

和 saveTickets 服务:

public class UserService {
private TicketDAO ticketDAO;
private UserDAO userDAO;

public void saveTicketsForUser(User user){
    if(user.getId()==null){
        //if user is not saved in database
        userDAO.save(user);
    }else{
        //if you have this client in database, you don't need to save client
    }
    for(Ticket ticket: user.getBookedTickets()){
        ticket.setUser(user);
        ticketDAO.save(ticket);
    }
}

}

您可以使用 xml.

将 dao classes 注入服务

解决方案是稍微重新设计一下逻辑。将主要部分保存逻辑移动到父摘要 class:

public abstract class BaseDAO<ENTITY extends BaseEntity> extends NamedParameterJdbcDaoSupport implements IGenericDao<ENTITY> {

    private final String tableName;
    private final Class<ENTITY> entityClass;
    private final List<String> fields;
    private final String insertSQL;
    private final String updateSQL;

    public BaseDAO(Class<ENTITY> entityClass, String tableName, List<String> fields) {
        this.entityClass = entityClass;
        this.tableName = tableName;
        this.fields = fields;

        // init SQLs
        StringBuilder sbInsertSQL = new StringBuilder();
        StringBuilder sbUpdateSQL = new StringBuilder();

        sbInsertSQL.append("INSERT INTO ").append(tableName).append(" (");
        sbUpdateSQL.append("UPDATE ").append(tableName).append(" SET ");

        for (int i = 0; i < fields.size(); i++) {
            if (i > 0) {
                sbInsertSQL.append(", ");
                sbUpdateSQL.append(", ");
            }
            sbInsertSQL.append(fields.get(i));
            sbUpdateSQL.append(fields.get(i)).append("=:").append(fields.get(i));
        }
        sbInsertSQL.append(") ").append("VALUES (");
        for (int i = 0; i < fields.size(); i++) {
            if (i > 0) {
                sbInsertSQL.append(",");
            }
            sbInsertSQL.append(":").append(fields.get(i));
        }

        sbInsertSQL.append(")\n");
        sbUpdateSQL.append(" WHERE id=:id\n");

        this.insertSQL = sbInsertSQL.toString();
        this.updateSQL = sbUpdateSQL.toString();
        Logger.debug("BaseDAO(), insertSQL: [" + insertSQL + "]");
        Logger.debug("BaseDAO(), updateSQL: [" + updateSQL + "]");
    }

    @Override
    public Long save(ENTITY entity) {
        long res;
        if (entity.getId() == null) {
            res = insert(entity);
        } else {
            update(entity);
            res = entity.getId();
        }
        return res;
    }

子 DAO 将如下所示:

public class UserDAO extends BaseDAO<User> {

    private static final String USER_TABLE_NAME = "t_user";
    private static final String userFields[] = {"name", "birthday", "email", "password", "role", "enabled"};

    public UserDAO() {
        super(User.class, USER_TABLE_NAME, Arrays.asList(userFields));
    }   

此外,使用户 table 具有自动递增的 ID:

----------------------
-- create t_user table
----------------------
CREATE TABLE t_user (
      id INT GENERATED ALWAYS AS IDENTITY CONSTRAINT pk_user PRIMARY KEY,
      name      VARCHAR(60) NOT NULL,
      birthday  DATE,
      email     VARCHAR(60),
      password  VARCHAR(100),
      role      VARCHAR(300),
      enabled   SMALLINT(6)
);

此外,模型应使用父逻辑进行更新:

public abstract class BaseEntity {

    protected Long id = null;
    protected String name;

public class User extends BaseEntity {
    private Date birthday;
    private String email;
    private String password;
    private String role;
    private boolean enabled;