I recently started working on my first ADF project using JDeveloper 12c.
So, I have a Fusion Web Application that's connected to an Oracle database. On one of the jsf pages there are two ADF tables, that display data from the database. Below is a button that sends a "DELETE"-statement to the database to delete selected entries. For reasons both tables have to be refreshed after this (the deletion affects the shown entries of both tables).
After I was already really happy that the tables were displaying the correct data and the button did its thing too I quickly realized that if the database changes, the tables in the jsf page will not get refreshed automatically. I searched around the web a bit for a a good beginner level tutorial of how to refresh elements of a jsf page in an ADF Fusion application. Sadly I didn't found anything that gave me the key to it so far.
I found this article on the Oracle HQ page, but it also has the habit of using many propietary namings and is written in flowing text, so there is no code sample or snippet or similar in it, which made it difficult to follow for a rookie.
This is a snippet from my Managed java bean, where I store my button functionality:
public String update() {
getDBConnection c = new DBConnection();
Connection conn = c.getConn();
RowKeySet selectedEntries = getDetailTable().getSelectedRowKeys();
Iterator selectedEntryIter = selectedEntries.iterator();
DCBindingContainer bindings = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
DCIteratorBinding entryIter = bindings.findIteratorBinding("my_iterator");
RowSetIterator rSIter = entryIter.getRowSetIterator();
try {
PreparedStatement pstmt1 = conn.prepareStatement("DELETE ...");
PreparedStatement pstmt2 = conn.prepareStatement("DELETE ...");
while(selectedEntryIter.hasNext()){
Key key = (Key)((List)selectedEntryIter.next()).get(0);
Row currentRow = rSIter.getRow(key);
BigDecimal barcode = (BigDecimal) currentRow.getAttribute("id");
BigDecimal field1 = (BigDecimal) currentRow.getAttribute("field1");
BigDecimal field2 = (BigDecimal) currentRow.getAttribute("field2");
pstmt1.setBigDecimal(1, id);
pstmt1.setBigDecimal(2, field1);
pstmt2.setBigDecimal(1, id);
pstmt2.setBigDecimal(2, field2);
pstmt1.executeUpdate();
pstmt2.executeUpdate();
}
conn.commit();
//i guess here i have to trigger to refresh the tables but I have pretty to no clue of how to do that
//where do I have to set the functionality? I read sth about creating another bean in the "session" package
//but somehow i have to access the jsf i want to have refreshed. Where do I create that connection?
//even a simple example or a good reference to a tutorial would be helpful for me
} catch (SQLException e) {
System.out.println(e.getMessage());
}
return null;
}
Probably this question is a duplicate to an already existing one and i'm just too stupid to find it, but I will give it a try anyways. Thanks in advance!
In your case you can simply add a entryIter.executeQuery(); right after your conn.commit(); (You should also avoid running a direct sql delete and use the standard ADF BC DELETE https://o7planning.org/11489/create-update-and-delele-data-using-adf-form-in-adf-bc#a9769791)
But to answer the question title for future queries, below is an exemple of a simple refresh table button, easily reusable, that i usually add on my clients tables toolbar :
//in your jsff
<af:panelCollection id="pc" >
<f:facet name="menus"/>
<f:facet name="statusbar"/>
<f:facet name="toolbar">
<af:toolbar id="t1" flex="5">
<af:group id="g1">
<af:commandImageLink shortDesc="Reload" partialSubmit="true" actionListener="#{YOUR_SCOPE.YOUR_BEAN.refreshTable}"
icon="#{resource['images:YOUR_RELOAD_ICON.png']}">
<f:attribute name="tableIdToRefresh" value="YOUR_TABLE_ID"/>
</af:commandImageLink>
</af:group>
</af:toolbar>
</f:facet>
<af:table id="YOUR_TABLE_ID" value="#{bindings.YOUR_VO.collectionModel}" var="row" rows="#{bindings.YOUR_VO.rangeSize}"
selectedRowKeys="#{bindings.YOUR_VO.collectionModel.selectedRow}" selectionListener="#{bindings.YOUR_VO.collectionModel.makeCurrent}" rowSelection="single"
fetchSize="#{bindings.YOUR_VO.rangeSize}" filterModel="#{bindings.XxcnVieIntSearchVCQuery.queryDescriptor}"
queryListener="#{bindings.XxcnVieIntSearchVCQuery.processQuery}" varStatus="vs" >
<!-- COLUMNS -->
</af:table>
</af:panelCollection>
//in your bean #{YOUR_SCOPE.YOUR_BEAN.refreshTable}
public void refreshTable(ActionEvent actionEvent) {
//Get the attribute tableIdToRefresh value. I like to have it as a jsff attribute so i can easily reuse the button elsewhere
String tableToRefreshId = "" + ((RichCommandImageLink)actionEvent.getSource()).getAttributes().get("tableIdToRefresh");
if (sValeurCode != null) {
addPprToComponentById(tableToRefreshId);
//If it doesn't suffice (see below) you can use this :
//refreshTableIterator(tableToRefreshId);
}
}
public static void addPprToComponentById(String id) {
Object component = findComponentInRoot(id); //from the great JSFUtils library
if (component != null) {
AdfFacesContext.getCurrentInstance().addPartialTarget((UIComponent)component);
AdfFacesContext.getCurrentInstance().partialUpdateNotify((UIComponent)component);
}
}
/**
* Locate an UIComponent in view root with its component id. Use a recursive way to achieve this.
* #param id UIComponent id
* #return UIComponent object
*/
public static UIComponent findComponentInRoot(String id) {
UIComponent component = null;
if (id != null) {
FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext != null) {
UIComponent root = facesContext.getViewRoot();
if (root != null) {
component = findComponent(root, id);
}
}
}
return component;
}
You can also use this addPPR logic for other components.
If a simple ppr doesn't suffice. You can force the table query execution with those :
public static void refreshTableIterator(String tableId) {
RichTable table = findComponentInRoot(tableId);
DCIteratorBinding treeIterator = null;
if (table != null) {
treeIterator = getTableIterator(table);
if (treeIterator != null) {
RowSetIterator rsi = treeIterator.getRowSetIterator();
treeIterator.executeQuery();
rsi.closeRowSetIterator();
}
}
}
public static DCIteratorBinding getTableIterator(RichTable table) {
DCIteratorBinding treeIterator = null;
CollectionModel model = (CollectionModel)table.getValue();
if (model != null) {
JUCtrlHierBinding treeBinding = (JUCtrlHierBinding)model.getWrappedData();
if (treeBinding != null) {
treeIterator = treeBinding.getDCIteratorBinding();
}
}
return treeIterator;
}
Related
public class Register {
#Autowired
private DataSource dataSource;
#Autowired
private DCNListener listener;
private OracleConnection oracleConnection = null;
private DatabaseChangeRegistration dcr = null;
private Statement statement = null;
private ResultSet rs = null;
#PostConstruct
public void init() {
this.register();
}
private void register() {
Properties props = new Properties();
props.put(OracleConnection.DCN_NOTIFY_ROWIDS, "true");
props.setProperty(OracleConnection.DCN_IGNORE_DELETEOP, "true");
props.setProperty(OracleConnection.DCN_IGNORE_UPDATEOP, "true");
try {
oracleConnection = (OracleConnection) dataSource.getConnection();
dcr = oracleConnection.registerDatabaseChangeNotification(props);
statement = oracleConnection.createStatement();
((OracleStatement) statement).setDatabaseChangeRegistration(dcr);
rs = statement.executeQuery(listenerQuery);
while (rs.next()) {
}
dcr.addListener(listener);
String[] tableNames = dcr.getTables();
Arrays.stream(tableNames)
.forEach(i -> log.debug("Table {}" + " registered.", i));
} catch (SQLException e) {
e.printStackTrace();
close();
}
}
}
My Listener:
public class DCNListener implements DatabaseChangeListener {
#Override
public void onDatabaseChangeNotification(DatabaseChangeEvent databaseChangeEvent) {
TableChangeDescription[] tableChanges = databaseChangeEvent.getTableChangeDescription();
for (TableChangeDescription tableChange : tableChanges) {
RowChangeDescription[] rcds = tableChange.getRowChangeDescription();
for (RowChangeDescription rcd : rcds) {
RowOperation op = rcd.getRowOperation();
String rowId = rcd.getRowid().stringValue();
switch (op) {
case INSERT:
//process
break;
case UPDATE:
//do nothing
break;
case DELETE:
//do nothing
break;
default:
//do nothing
}
}
}
}
}
In my Spring boot application, I have an Oracle DCN Register class that listens for INSERTS in an event table of my database. I am listening for insertion new records.
In this Event table, I have different types of events that my application supports, lets say EventA and EventB.
The application gui allows you to upload in bulk these type of events which translate into INSERT into the oracle database table I am listening to.
For one of the event types, my application is not capturing the INSERT ONLY when it is 20 or more events uploaded in bulk, but for the other event type, I do not experience this problem.
So lets say user inserts eventA any number < 20, my application captures the inserts. But if the number of eventA inserts exceeds 20, it does not capture.
This is not the case for eventB which works smoothly. I'd like to understand if I'm missing anything in term of registration and anything I can look out for maybe in the database or what the issue could be here?
You should also look for the ALL_ROWS event from:
EnumSet<TableChangeDescription.TableOperation> tableOps = tableChange.getTableOperations();
if(tableOps.contains(TableChangeDescription.TableOperation.ALL_ROWS)){
// Invalidate the cache
}
Quote fromt the JavaDoc:
The ALL_ROWS event is sent when the table is completely invalidated and row level information isn't available. If the DCN_NOTIFY_ROWIDS option hasn't been turned on during registration, then all events will have this OPERATION_ALL_ROWS flag on. It can also happen in situations where too many rows have changed and it would be too expensive for the server to send the list of them.
https://docs.oracle.com/en/database/oracle/oracle-database/12.2/jajdb/oracle/jdbc/dcn/TableChangeDescription.TableOperation.html#ALL_ROWS
I'm going to ask a huge favor here.
I have a view that when it opens, it should show every beverage from the database, and show that on the screen.
It also has to add a + button, an amount label next to it, and a - button. This should be done for every item.
The tables I'm getting the items from is called dhh_item by the way.
Now, I've got this:
public ArrayList<Item> getBeverages(Item item) {
ArrayList<Item> items = new ArrayList<>();
if (item != null) {
// First open a database connnection
DatabaseConnection connection = new DatabaseConnection();
if (connection.openConnection()) {
// If a connection was successfully setup, execute the SELECT statement.
ResultSet resultset = connection.executeSQLSelectStatement(
"SELECT * FROM dhh_item ");
if (resultset != null) {
try {
while (resultset.next()) {
String itemName = resultset.getString("itemName");
String status = resultset.getString("status");
String description = resultset.getString("description");
int price = resultset.getInt("price");
Item newItem = new Item(itemName, status, description, price);
items.add(newItem);
}
} catch (SQLException e) {
System.out.println(e);
items.clear();
}
}
// else an error occurred leave array list empty.
// We had a database connection opened. Since we're finished,
// we need to close it.
connection.closeConnection();
}
}
return items;
}
Is this correct in any way. Would I retrieve any data at all? (The .getString()'s are correct.)
Now, this method is inside of another Class (ItemDAO).
Can I call this from my view? How would I get it to make a new label + button for each?
Thanks a lot for those who could help me out on this one!
At the end, it should be looking like this:
for each beverage in the table.
Sounds rather straigh forward
Collection<Item> items=dao.getBeverages(someItem) // get all items
for(Item item:items){
label=new JLabel(item.getYourItemNameOrLabelOrhatever) // this will be the "coca-cola"
incButton=new JButton(incrementButtonAction); // craete/get some action
decButton=new JButton(decrementButtonAction); // same here
counter=new JLabel("0");
yourContainer.add(label);
yourContainer.add(incButton);
yourContainer.add(label);
yourContainer.add(decButton);
yourContainer.revalidate();
}
I've a question on hibernate operation: update.
Here a bit of code:
Campaign campaign = campaignDAO.get(id);
campaign.setStatus(true);
campaignDAO.update(campaign);
If I just have all the data of the campaign object, is there any way to perform an update without perform the first select (campaignDAO.get(id)) ?
Thanks,
Alessio
HQL will definitely help you.
In order to maintain the separation of concerns, you can add a more specialized method in you DAO object:
public void updateStatusForId(long id, boolean status){
//provided you obtain a reference to your session object
session.createQuery("UPDATE Campaign SET status = " + status + " WHERE id = :id").setParameter("id", id).executeUpdate();
//flush your session
}
Then you could simply call this method from your business method. You can check the generated SQL statements inside the logs of your app by setting the show_sql hibernate property to true.
You can use session.load(). It will not hit the database. Here you can find its details and example code.
I had worte a extension to solve this issue in Nhibernate
how to use!
first of all you need enable dynamic-update="true"
using (ISession session = sessionFactory.OpenSession())
{
Customer c1 = new Customer();
c1.CustomerID = c.CustomerID;
session.Mark(c1);
// c1.Name = DateTime.Now.ToString();
c1.Phone = DateTime.Now.ToString();
//需要开启动态更新
session.UpdateDirty(c1);
session.Flush();
}
UpdateExtension.cs
public static class UpdateExtension
{
static readonly Object NOTNULL = new Object();
public static void UpdateDirty<TEntity>(this ISession session, TEntity entity)
{
SessionImpl implementor = session as SessionImpl;
EntityEntry entry = implementor.PersistenceContext.GetEntry(entity);
if (entry == null)
{
throw new InvalidOperationException("找不到对应的实例,请先使用Mask方法标记");
}
IEntityPersister persister = entry.Persister;
// 如果某列不可以为空,新的Entity里也不想更新他。
// 那么LoadState 里的值应该和Entity 中的值相同
Object[] CurrentState = entry.Persister.GetPropertyValues(entity, EntityMode.Poco);
Object[] LoadedState = entry.LoadedState;
int[] dirtys = persister.FindDirty(CurrentState
, LoadedState
, entity
, (SessionImpl)session);
if (dirtys == null || dirtys.Length == 0)
{
return;
}
persister.Update(entry.Id
, CurrentState
, dirtys
, true
, LoadedState
, entry.Version
, entity
, entry.RowId
, (SessionImpl)session);
implementor.PersistenceContext.RemoveEntry(entity);
implementor.PersistenceContext.RemoveEntity(entry.EntityKey);
session.Lock(entity, LockMode.None);
// 防止(implementor.PersistenceContext.EntityEntries.Count == 0)
}
public static void Mark<TEntity>(this ISession session, TEntity entity)
{
session.Lock(entity, LockMode.None);
}
}
here is update sql
command 0:UPDATE Customers SET Phone = #p0 WHERE CustomerID = #p1;#p0 = '2014/12/26 0:12:56' [Type: String (4000)], #p1 = 1 [Type: Int32 (0)]
Only update Phone column .
event Name property can not be null. we can work very well.
Ive tried a thousand things. As of right now the only way for me to query anything is to get the entire list and look through it that way! which takes way to much time. How can I query something in google app engine, for example pull only the entities that have > 100 votes for example.
Im following the tic-tac-toe example https://github.com/GoogleCloudPlatform/appengine-endpoints-tictactoe-java and https://developers.google.com/eclipse/docs/endpoints-addentities
In the example I just switched notes for quotes.
Heres my current code for example on how im getting the entities
this is an async task and its loading each one which takes to long
protected CollectionResponseQuotes doInBackground(Context... contexts) {
Quotesendpoint.Builder endpointBuilder = new Quotesendpoint.Builder(
AndroidHttp.newCompatibleTransport(),
new JacksonFactory(),
new HttpRequestInitializer() {
public void initialize(HttpRequest httpRequest) { }
});
Quotesendpoint endpoint = CloudEndpointUtils.updateBuilder(
endpointBuilder).build();
try {
quotes = endpoint.listquotes().execute();
for (Quotes quote : quotes.getItems()) {
if (quote.getVotes() > 3) {
quoteList.add(quote);
}
}
Here is the code that Google generated in the app engine for me when I created the endpoint. It looks like it will query somehow but I cant figure it out. They are two different projects.
#Api(name = "quotesendpoint", namespace = #ApiNamespace(ownerDomain = "projectquotes.com", ownerName = "projectquotes.com", packagePath = ""))
public class quotesEndpoint {
/**
* This method lists all the entities inserted in datastore.
* It uses HTTP GET method and paging support.
*
* #return A CollectionResponse class containing the list of all entities
* persisted and a cursor to the next page.
*/
#SuppressWarnings({ "unchecked", "unused" })
#ApiMethod(name = "listquotes")
public CollectionResponse<quotes> listquotes(
#Nullable #Named("cursor") String cursorString,
#Nullable #Named("limit") Integer limit) {
EntityManager mgr = null;
Cursor cursor = null;
List<quotes> execute = null;
try {
mgr = getEntityManager();
Query query = mgr.createQuery("select from quotes as quotes");
if (cursorString != null && cursorString != "") {
cursor = Cursor.fromWebSafeString(cursorString);
query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
}
if (limit != null) {
query.setFirstResult(0);
query.setMaxResults(limit);
}
execute = (List<quotes>) query.getResultList();
cursor = JPACursorHelper.getCursor(execute);
if (cursor != null)
cursorString = cursor.toWebSafeString();
// Tight loop for fetching all entities from datastore and accomodate
// for lazy fetch.
for (quotes obj : execute)
;
} finally {
mgr.close();
}
return CollectionResponse.<quotes> builder().setItems(execute)
.setNextPageToken(cursorString).build();
}
/**
* This method gets the entity having primary key id. It uses HTTP GET method.
*
* #param id the primary key of the java bean.
* #return The entity with primary key id.
*/
#ApiMethod(name = "getquotes")
public quotes getquotes(#Named("id") String id) {
EntityManager mgr = getEntityManager();
quotes quotes = null;
try {
quotes = mgr.find(quotes.class, id);
} finally {
mgr.close();
}
return quotes;
}
Tried to user cursor but now sure how it works. Ive tried
Cursor cursor = db.rawQuery("select * from Votes WHERE Votes >" + 250 , null);
quotes = endpoint.listquotes().setCursor(cursor).execute();
Did you try to pass parameters to endpoint.listquotes()?
Specifically parameter "limit" to limit a number of results and parameter "cursor" to change selection criteria?
I'd like to use a database to store i18n key/value pairs so we can modify / reload the i18n data at runtime. Has anyone done this? Or does anyone have an idea of how to implement this? I've read several threads on this, but I haven't seen a workable solution.
I'm specifically refering to something that would work with the jstl tags such as
<fmt:setlocale>
<fmt:bundle>
<fmt:setBundle>
<fmt:message>
I think this will involve extending ResourceBundle, but when I tried this I ran into problems that had to do with the way the jstl tags get the resource bundle.
I finally got this working with danb's help above.
This is my resource bundle class and resource bundle control class.
I used this code from #[danb]'s.
ResourceBundle bundle = ResourceBundle.getBundle("AwesomeBundle", locale, DbResourceBundle.getMyControl());
javax.servlet.jsp.jstl.core.Config.set(actionBeanContext.getRequest(), Config.FMT_LOCALIZATION_CONTEXT, new LocalizationContext(bundle, locale));
and wrote this class.
public class DbResourceBundle extends ResourceBundle
{
private Properties properties;
public DbResourceBundle(Properties inProperties)
{
properties = inProperties;
}
#Override
#SuppressWarnings(value = { "unchecked" })
public Enumeration<String> getKeys()
{
return properties != null ? ((Enumeration<String>) properties.propertyNames()) : null;
}
#Override
protected Object handleGetObject(String key)
{
return properties.getProperty(key);
}
public static ResourceBundle.Control getMyControl()
{
return new ResourceBundle.Control()
{
#Override
public List<String> getFormats(String baseName)
{
if (baseName == null)
{
throw new NullPointerException();
}
return Arrays.asList("db");
}
#Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException,
InstantiationException, IOException
{
if ((baseName == null) || (locale == null) || (format == null) || (loader == null))
throw new NullPointerException();
ResourceBundle bundle = null;
if (format.equals("db"))
{
Properties p = new Properties();
DataSource ds = (DataSource) ContextFactory.getApplicationContext().getBean("clinicalDataSource");
Connection con = null;
Statement s = null;
ResultSet rs = null;
try
{
con = ds.getConnection();
StringBuilder query = new StringBuilder();
query.append("select label, value from i18n where bundle='" + StringEscapeUtils.escapeSql(baseName) + "' ");
if (locale != null)
{
if (StringUtils.isNotBlank(locale.getCountry()))
{
query.append("and country='" + escapeSql(locale.getCountry()) + "' ");
}
if (StringUtils.isNotBlank(locale.getLanguage()))
{
query.append("and language='" + escapeSql(locale.getLanguage()) + "' ");
}
if (StringUtils.isNotBlank(locale.getVariant()))
{
query.append("and variant='" + escapeSql(locale.getVariant()) + "' ");
}
}
s = con.createStatement();
rs = s.executeQuery(query.toString());
while (rs.next())
{
p.setProperty(rs.getString(1), rs.getString(2));
}
}
catch (Exception e)
{
e.printStackTrace();
throw new RuntimeException("Can not build properties: " + e);
}
finally
{
DbUtils.closeQuietly(con, s, rs);
}
bundle = new DbResourceBundle(p);
}
return bundle;
}
#Override
public long getTimeToLive(String baseName, Locale locale)
{
return 1000 * 60 * 30;
}
#Override
public boolean needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime)
{
return true;
}
};
}
Are you just asking how to store UTF-8/16 characters in a DB? in mysql it's just a matter of making sure you build with UTF8 support and setting that as the default, or specifying it at the column or table level. I've done this in oracle and mysql before. Create a table and cut and paste some i18n data into it and see what happens... you might be set already..
or am I completely missing your point?
edit:
to be more explicit... I usually implement a three column table... language, key, value... where "value" contains potentially foreign language words or phrases... "language" contains some language key and "key" is an english key (i.e. login.error.password.dup)... language and key are indexed...
I've then built interfaces on a structure like this that shows each key with all its translations (values)... it can get fancy and include audit trails and "dirty" markers and all the other stuff you need to enable translators and data entry folk to make use of it..
Edit 2:
Now that you added the info about the JSTL tags, I understand a bit more... I've never done that myself.. but I found this old info on theserverside...
HttpSession session = .. [get hold of the session]
ResourceBundle bundle = new PropertyResourceBundle(toInputStream(myOwnProperties)) [toInputStream just stores the properties into an inputstream]
Locale locale = .. [get hold of the locale]
javax.servlet.jsp.jstl.core.Config.set(session, Config.FMT_LOCALIZATION_CONTEXT, new LocalizationContext(bundle ,locale));
We have a database table with key/language/term where key is a n integer and is a combined primary key together with language.
We are using Struts, so we ended up writing our own PropertyMessageResources implementation which allows us to do something like <bean:message key="impressum.text" />.
It works very well and gives us the flexibility to do dynamically switch languages in the front-end as well as updating the translations on the fly.
Actuly what ScArcher2 needed is davids response which is not marked a correct or helpfull.
The solution ScArcher2 chose to use is imo terrible mestake:) Loading ALL the translations at one time... in any bigger application its gonna kill it. Loading thousends of translations each request...
david's method is more commonly used in real production environments.
Sometimes to limit db calls, which is with every message translated, you can create groups of translations by topic, functionality etc. to preload them. But this is little bit more complex and can be substituted with good cache system.